Interoperabilidad en Swift: C, Objective-C, bridging headers y Swift C++ interop

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 SwiftEquivalente CUso
UnsafePointer<T>const T*Leer sin modificar
UnsafeMutablePointer<T>T*Leer y modificar
UnsafeRawPointerconst void*Bytes sin tipo
UnsafeMutableRawPointervoid*Bytes sin tipo, modificable
OpaquePointerpuntero opacoTipos 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 throws de 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.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP