Swift no vive en un vacío: millones de líneas de código en Objective-C y C siguen en producción, y el ecosistema Apple tiene APIs que solo existen en esos lenguajes. La interoperabilidad de Swift con C y Objective-C no es un añadido de compatibilidad, sino una característica de primera clase del lenguaje diseñada para coexistir con el código existente sin fricciones. Y desde Swift 5.9, la interoperabilidad con C++ es directa sin necesidad de wrappers manuales.
Bridging header: usar Objective-C desde Swift
El bridging header es un fichero .h que le dice al compilador de Swift qué cabeceras de Objective-C deben estar disponibles en tu código Swift:
// MiApp-Bridging-Header.h
#import "LegacyManager.h"
#import "AnalyticsSDK/Analytics.h"
#import <CommonCrypto/CommonCrypto.h>
Xcode lo crea automáticamente la primera vez que añades un fichero Objective-C a un proyecto Swift. También puedes especificarlo manualmente en Build Settings > "Objective-C Bridging Header".
// Una vez importado, usas las clases Objective-C directamente
let manager = LegacyManager()
manager.cargarDatos { datos, error in
guard error == nil else { return }
// datos es [AnyHashable: Any]? el tipo dinámico de NSDictionary
}
// Los tipos se puentean automáticamente:
// NSString ? String, NSArray ? [Any], NSDictionary ? [AnyHashable: Any]
// NSNumber ? Int/Double/Bool, etc.
@objc y @objcMembers: exponer Swift a Objective-C
Para que código Objective-C (o el runtime ObjC) pueda llamar a tu código Swift, necesitas anotarlo:
// Método individual
class MiServicio: NSObject {
@objc func procesarDatos(_ datos: Data) -> Bool {
// Este método es visible para ObjC y para el runtime (KVO, selectors, etc.)
return true
}
}
// Toda la clase
@objcMembers
class EventTracker: NSObject {
var nombre: String = ""
var timestamp: Date = .now
func registrar() {
// Todos los métodos y propiedades son automáticamente @objc
}
}
// Para protocolos que serán usados como delegados ObjC:
@objc protocol MiDelegado: AnyObject {
func hizo(_ accion: String)
@objc optional func puedeHacer(_ accion: String) -> Bool // Método opcional
}
Módulos C con module.modulemap
Para usar una librería C en Swift sin Objective-C de por medio, necesitas un modulemap que describe la librería:
// Supón que tienes una librería C: libmicrypto.h + libmicrypto.a
// module.modulemap (en el directorio de la librería)
module MiCrypto {
header "libmicrypto.h"
link "micrypto"
export *
}
// En Package.swift:
.systemLibrary(
name: "MiCrypto",
path: "Sources/MiCrypto",
pkgConfig: "micrypto"
)
// En tu código Swift:
import MiCrypto
let resultado = mi_hash_sha256(datos, datos.count)
UnsafePointer: interoperabilidad con punteros C
Las APIs C usan punteros directamente. Swift tiene tipos seguros para gestionarlos:
// Función C: int proceso(const char* datos, int longitud, char* salida);
// En Swift:
func llamarFuncionC(entrada: String) -> String? {
// Acceder a los bytes de un String como UnsafePointer<CChar>
return entrada.withCString { ptrEntrada in
var buffer = [CChar](repeating: 0, count: 256)
let exito = proceso(ptrEntrada, Int32(entrada.utf8.count), &buffer)
guard exito == 0 else { return nil }
return String(cString: buffer)
}
}
// Para Data: withUnsafeBytes
func procesarData(_ datos: Data) -> Data {
datos.withUnsafeBytes { ptrEntrada in
var salida = Data(count: datos.count * 2)
salida.withUnsafeMutableBytes { ptrSalida in
transformar(
ptrEntrada.baseAddress!.assumingMemoryBound(to: UInt8.self),
ptrSalida.baseAddress!.assumingMemoryBound(to: UInt8.self),
datos.count
)
}
return salida
}
}
Tipos de puntero en Swift
| Tipo Swift | Equivalente C | Uso |
|---|---|---|
UnsafePointer<T> | const T* | Leer sin modificar |
UnsafeMutablePointer<T> | T* | Leer y modificar |
UnsafeRawPointer | const void* | Bytes sin tipo |
UnsafeMutableRawPointer | void* | Bytes sin tipo, modificable |
OpaquePointer | puntero opaco | Tipos C que Swift no conoce |
Interoperabilidad C++ directa (Swift 5.9)
Desde Swift 5.9, puedes usar tipos y funciones de C++ directamente sin un wrapper en Objective-C, simplemente habilitando la interoperabilidad en el compilador:
// Package.swift
.target(
name: "MiApp",
swiftSettings: [
.interoperabilityMode(.Cxx)
]
)
// procesador.h (C++)
#pragma once
#include <string>
#include <vector>
class Procesador {
public:
std::string procesar(const std::string& entrada);
std::vector<int> calcularEstadisticas(const std::vector<double>& datos);
};
// En Swift, importas y usas directamente
import CxxModule
var proc = Procesador()
let resultado = proc.procesar(std.string("entrada de datos"))
// std::vector ? [T] se convierte automáticamente para tipos simples
let datos: [Double] = [1.5, 2.3, 4.1, 3.7]
let stats = proc.calcularEstadisticas(datos)
Limitaciones de la interoperabilidad C++
No todo C++ puede usarse directamente desde Swift:
- Las plantillas (templates) de C++ deben ser instanciadas explícitamente
- Las excepciones de C++ no mapean a
throwsde Swift automáticamente - Los tipos con constructores de copia no triviales requieren anotaciones especiales (
SWIFT_COPYABLE) - La herencia múltiple de C++ no tiene equivalente en Swift
Cuándo usar cada enfoque
- Bridging header: librerías Objective-C existentes, SDKs de terceros en ObjC
- @objc / @objcMembers: exponer Swift a runtime ObjC (delegados, KVO, UIKit legacy)
- modulemap: librerías C puras (CommonCrypto, SQLite, libxml2)
- UnsafePointer: llamadas C puntuales donde el tipo no se puede auto-bridgear
- C++ interop: bases de código C++ grandes donde crear wrappers ObjC sería prohibitivo
Resumen
Swift coexiste con C, Objective-C y C++ de forma pragmática: el bridging header y @objc cubren la interoperabilidad con el ecosistema Apple legacy, los modulemaps exponen librerías C sin necesidad de wrappers, y la interoperabilidad C++ directa de Swift 5.9 elimina el intermediario de Objective-C para bases de código C++. Los punteros unsafe son el escape hatch para operaciones de bajo nivel que el sistema de tipos no puede expresar de otra forma, y deben confinarse a funciones cortas con límites claros.
