Cómo incrustar una biblioteca C en un marco Swift



En 2014, se introdujo Swift, un nuevo lenguaje para desarrollar las aplicaciones del ecosistema de Apple. La novedad trajo no solo nuevas características y funciones, sino también problemas, para aquellos que querían usar las buenas bibliotecas C antiguas. En este artículo, analizaré uno de ellos: la agrupación de la biblioteca C en el marco Swift. Hay varias formas de resolverlo; en este caso, explicaré cómo hacer esto con módulos explícitos clang.

Por ejemplo, tomamos la biblioteca C libgif externa y la incorporamos en nuestro marco Swift GifSwift. Si desea ver el resultado de inmediato, puede ver el proyecto completo aquí .

Preparando libgif


Antes de incrustar la biblioteca libgif en nuestro proyecto, debe compilarla desde la fuente.

  1. Descargue el último tarball aquí .
  2. Descomprima el archivo, usando la consola, vaya a la carpeta y ejecute:

    ./configure && make check 

    Nota: para simplificar, estamos compilando una biblioteca para la plataforma x86-64 y, por lo tanto, solo funcionará en el simulador de iOS o en macOS. La construcción de una biblioteca estática de arquitectura múltiple es un tema separado, que no menciono en este artículo. Las instrucciones útiles se pueden encontrar aquí .
  3. Si todo va sin errores, los archivos de la biblioteca se pueden encontrar en ${lib_gif_source}/lib/.libs . Estamos interesados ​​en dos archivos:

     lib/.libs/libgif.a #   lib/gif_lib.h #  

Configuración del proyecto


Ahora personalizaremos el proyecto a nuestras necesidades.

  1. Cree un nuevo proyecto utilizando la plantilla Cocoa Touch Framework, asígnele el nombre GifSwift .
  2. Agregue los archivos de la biblioteca libgif que creamos a un grupo separado dentro del proyecto.
  3. Agregue un nuevo objetivo para la aplicación de prueba al proyecto para ver el resultado.

La estructura final del proyecto debería verse así:



Importar a Swift


Para importar una biblioteca C en Swift, debemos describirla como un módulo . La descripción es un archivo .modulemap que contiene una lista de archivos de encabezado para importar y bibliotecas estáticas para vincular. El módulo resultante se puede importar a Swift o al código Objective-C (usando @import ).

Este método de importación de la biblioteca en el marco funcionará en la mayoría de los casos (lea más sobre este enfoque aquí ). Funciona muy bien si está creando un marco interno o simplemente dividiendo su aplicación en módulos. Pero este método también tiene desventajas. Por ejemplo, es ineficaz si desea transferir su biblioteca a alguien que usa Cartago, Cocoapods o debido a un artefacto binario. La razón es que el marco resultante generalmente no es portátil, porque al compilar está vinculado a una ubicación específica de los archivos de encabezado y bibliotecas del mapa de módulos en su computadora.

Módulo explícito


Para sortear estas limitaciones, utilizaremos otra forma: el módulo explícito para la biblioteca. Un módulo explícito es un módulo que se declara como un submódulo utilizando la palabra clave explícita , se coloca en el módulo principal y no se importa automáticamente. Funciona de manera similar a *_Private.h para los *_Private.h Objective-C. Si desea utilizar la API declarada en él, debe importar el módulo explícitamente (explícitamente).

Estamos creando un módulo explícito para la biblioteca C dentro del marco. Para hacer esto, necesitamos redefinir el módulo Xcode generado. Además, tenga en cuenta que no especificamos la biblioteca libgif.a para link (link gif), sino que lo hacemos directamente en el proyecto utilizando la interfaz Xcode.

Nota: para obtener más información sobre módulos explícitos , haga clic aquí.

  1. Agregue un archivo llamado GifSwift.modulemap a la carpeta raíz del proyecto:

     framework module GifSwift { umbrella header "GifSwift.h" explicit module CLibgif { private header "gif_lib.h" } export * } 

    Este archivo contiene la especificación del módulo CLibgif explícito y consta de un archivo de encabezado declarado (ya que solo hay uno en nuestra biblioteca). El archivo se carga en el módulo resultante para el marco.
  2. El archivo de descripción del módulo no necesita agregarse al marco, pero debe especificarse en la configuración de destino:

     Build Settings — Packaging — Module Map (MODULEMAP_FILE) = $SRCROOT/GifSwift/GifSwift.modulemap 
  3. Los archivos libgif deben agregarse al objetivo del marco como un encabezado privado ( gif_lib.h ) y una biblioteca estática ( libgif.a ). Tenga en cuenta que el archivo de encabezado para la biblioteca C se ha agregado al destino como privado. Esto es necesario para nuestro módulo explícito. Nada impide agregar este archivo de encabezado como público, pero nuestra tarea es ocultar los detalles de implementación lo más simple posible.


  4. Ahora puede importar el módulo explícito dentro del marco utilizando import GifSwift.CLibgif

Envoltura rápida


Ahora puedes hacer la interfaz de nuestro framework. Una clase es suficiente, que es un gif con un par de propiedades:

 import Foundation import GifSwift.CLibgif public class GifFile { private let path: URL private let fileHandlePtr: UnsafeMutablePointer<GifFileType> private var fileHandle: GifFileType { return self.fileHandlePtr.pointee } deinit { DGifCloseFile(self.fileHandlePtr, nil) } // MARK: - API public init?(path: URL) { self.path = path let errorCode = UnsafeMutablePointer<Int32>.allocate(capacity: 1) if let handle = path.path.withCString({ DGifOpenFileName($0, errorCode) }) { self.fileHandlePtr = handle DGifSlurp(handle) } else { debugPrint("Error opening file \(errorCode.pointee)") return nil } } public var size: CGSize { return CGSize(width: Double(fileHandle.SWidth), height: Double(fileHandle.SHeight)) } public var imagesCount: Int { return Int(fileHandle.ImageCount) } } 

GifFile.swift envuelve interfaces de programación de bajo nivel para procesar archivos y accede a algunas propiedades, asignándolas a tipos de Foundation más convenientes.

Cheque


Para probar nuestra biblioteca, agregué el archivo cat.gif al proyecto:

 import UIKit import GifSwift class ViewController: UIViewController {   override func viewDidLoad() {       super.viewDidLoad()       if let file = GifFile(path: Bundle.main.url(forResource: "cat", withExtension: "gif")!) {           debugPrint("Image has size: \(file.size) and contains \(file.imagesCount) images")       }   } } 

Cuando ejecutamos este código en la consola, veremos lo siguiente:

"La imagen tiene tamaño: (250.0, 208.0) y contiene 44 imágenes"

Conclusiones


El marco resultante contiene todo lo que necesita usar, tiene una interfaz Swift y, de forma predeterminada, oculta el código C de los clientes. Sin embargo, esto no es del todo cierto. Como escribí anteriormente, la importación de GifSwift.CLibgif le da acceso a todos los módulos privados, sin embargo, por defecto, este método de encapsulación es suficiente para ocultar los detalles de la implementación del marco.

Source: https://habr.com/ru/post/es435650/


All Articles