Posts

Cross-import overlays en Swift: por qué algunas APIs necesitan dos importaciones

Arturo Rivas Arias

En ocasiones, Xcode asegura que una API no existe aunque aparezca claramente en la documentación de Apple. El nombre está bien escrito, la versión mínima del sistema es correcta y el tipo parece pertenecer al módulo que ya hemos importado. Sin embargo, el compilador responde con un cannot find ... in scope o indica que una vista no contiene el modificador que queremos utilizar.

Detrás de algunos de estos errores están los cross-import overlays: módulos auxiliares que Swift carga automáticamente cuando un mismo archivo importa una dupla concreta de módulos. No solemos verlos ni escribir su nombre, pero explican por qué determinadas APIs solo aparecen al combinar dos import.

Un tercer módulo que se carga de forma automática

Una API que conecta dos tecnologías necesita conocer los tipos de ambas. Una vista para reproducir vídeo, por ejemplo, depende de SwiftUI para participar en su jerarquía de vistas y de AVKit para trabajar con la reproducción multimedia.

Apple podría colocar toda esa integración dentro de SwiftUI, pero entonces SwiftUI tendría que depender de AVKit incluso en aplicaciones que nunca reproducen vídeo. También podría incluirla directamente en AVKit, a costa de introducir la dependencia contraria. Ninguna opción escala bien cuando el SDK contiene decenas de integraciones similares.

La solución consiste en mantener independientes los módulos principales y trasladar la API que los conecta a un tercero. Cuando el compilador encuentra las dos importaciones en el mismo archivo, descubre la regla asociada y carga también ese módulo de superposición. El orden de los import no importa; la presencia de ambos sí.

Este es el motivo por el que MapKit y SwiftUI activan internamente _MapKit_SwiftUI. El nombre comienza con un guion bajo porque no forma parte de la interfaz que deberíamos importar o utilizar directamente. Es un detalle de implementación del SDK.

Un ejemplo con Quick Look

El modificador .quickLookPreview(_:) permite presentar la previsualización de un archivo desde una vista de SwiftUI. Importar únicamente SwiftUI no basta, porque esa API pertenece a la integración entre SwiftUI y Quick Look.

import SwiftUI
import QuickLook

struct ReportRow: View {
    let reportURL: URL
    @State private var previewURL: URL?

    var body: some View {
        Button("Previsualizar informe") {
            previewURL = reportURL
        }
        .quickLookPreview($previewURL)
    }
}

SwiftUI aporta View, Button y @State; QuickLook activa la integración que añade el modificador. Si eliminamos esta segunda importación, la jerarquía continúa siendo válida, pero .quickLookPreview deja de estar disponible. El error puede resultar desconcertante porque la documentación muestra el modificador como parte de View, sin hacer visible todo el mecanismo que lo incorpora.

El mismo patrón aparece en otras APIs habituales:

Módulos importadosAPIs que aparecen
SwiftUI y MapKitMap, Marker y Annotation
SwiftUI y PhotosUIPhotosPicker
SwiftUI y WebKitWebView
SwiftUI y AVKitVideoPlayer
SwiftUI y QuickLook.quickLookPreview(_:)

Aunque SwiftUI concentra buena parte de los casos más visibles, el mecanismo no está limitado a la interfaz. El SDK también contiene superposiciones para parejas como SwiftData y CoreData, o CoreData y CloudKit.

Cómo descubre Swift estas relaciones

Los frameworks del SDK pueden incluir un directorio con la extensión .swiftcrossimport. Dentro aparecen archivos .swiftoverlay cuyo nombre identifica el segundo módulo de la dupla. Su contenido es una pequeña descripción en YAML con el nombre del módulo auxiliar que debe cargar el compilador.

La estructura simplificada tiene este aspecto:

FrameworkA.framework/Modules/
└── FrameworkA.swiftcrossimport/
    └── FrameworkB.swiftoverlay

Es posible inspeccionar todas las reglas incluidas en un SDK desde Terminal:

SDK_PATH="$(xcrun --sdk iphonesimulator --show-sdk-path)"

find "$SDK_PATH/System/Library/Frameworks" \
    -path "*.swiftcrossimport/*.swiftoverlay" \
    -print

Este listado también ayuda a descubrir integraciones que no resultan obvias al consultar el navegador de documentación de Xcode.

Por qué el error aparece solo en algunos archivos

Las importaciones de Swift se aplican al archivo fuente que las declara. Que otro archivo del mismo target importe QuickLook no activa automáticamente su superposición donde estamos escribiendo la vista. La dupla debe estar presente en el archivo que utiliza la API.

Esto explica un caso especialmente confuso: copiar una vista entre dos archivos puede hacer que deje de compilar sin haber cambiado una sola línea de su implementación. El archivo original ya contenía la segunda importación y el nuevo no. También afecta al completado de código: Xcode no puede proponer símbolos de un módulo que todavía no ha cargado, de modo que la API ni siquiera aparece entre las sugerencias.

Una estrategia de diagnóstico

Cuando un símbolo documentado no aparece, conviene comprobar primero qué tecnologías conecta. Una vista que muestra fotografías probablemente necesite PhotosUI; una integración web, WebKit; una previsualización de documentos, QuickLook. Añadir el módulo correspondiente en el mismo archivo suele ofrecer una respuesta más rápida que borrar DerivedData o revisar el destino mínimo de despliegue.

Después hay que verificar la disponibilidad de la API, porque una importación correcta no elimina sus requisitos de versión. Los cross-import overlays resuelven qué módulos participan, mientras que @available y #available siguen determinando en qué sistemas puede ejecutarse el código.

Tampoco conviene importar directamente módulos internos como _MapKit_SwiftUI. Sus nombres y su organización pueden cambiar, no constituyen una interfaz pública y omiten la intención real del código. Declarar los dos módulos públicos mantiene la dependencia explícita y permite que el compilador seleccione la superposición adecuada.

Una pieza invisible con consecuencias muy visibles

Los cross-import overlays permiten ampliar el SDK sin convertir cada módulo en una red de dependencias innecesarias. Para quien desarrolla una aplicación, su funcionamiento se resume en una regla práctica: cuando una API une dos tecnologías, ambas importaciones deben aparecer en el archivo que la utiliza.

Entender ese tercer módulo invisible convierte muchos errores de ámbito aparentemente arbitrarios en un problema sencillo de dependencias. No falta el tipo ni falla Xcode: todavía no se ha formado la pareja de importaciones que permite al compilador cargarlo.