Comment intégrer une bibliothèque C dans un framework Swift



En 2014, Swift a été introduit, un nouveau langage pour développer les applications de l'écosystème d'Apple. La nouveauté a apporté non seulement de nouvelles fonctionnalités et fonctions, mais aussi des problèmes - pour ceux qui voulaient utiliser les bonnes vieilles bibliothèques C. Dans cet article, j'examinerai l'un d'entre eux - le regroupement de bibliothèques C dans le cadre Swift. Il existe plusieurs façons de le résoudre; dans ce cas, je vais vous expliquer comment faire cela avec des modules explicites clang.

Par exemple, nous prenons la bibliothèque C externe de libgif et l'intégrons dans notre framework Swift GifSwift. Si vous souhaitez voir immédiatement le résultat, le projet complet peut être consulté ici .

Préparation de libgif


Avant d'incorporer la bibliothèque libgif dans notre projet, vous devez la compiler à partir de la source.

  1. Téléchargez la dernière tarball ici .
  2. Décompressez l'archive, à l'aide de la console, accédez au dossier et exécutez:

    ./configure && make check 

    Remarque: pour plus de simplicité, nous compilons une bibliothèque pour la plate-forme x86-64, et donc elle ne fonctionnera que dans le simulateur iOS ou sur macOS. La construction d'une bibliothèque statique multi-architecture est un sujet distinct, que je n'aborde pas dans cet article. Des instructions utiles peuvent être trouvées ici .
  3. Si tout se passe sans erreurs, les fichiers de bibliothèque peuvent être trouvés dans ${lib_gif_source}/lib/.libs . Nous sommes intéressés par deux fichiers:

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

Configuration du projet


Nous allons maintenant personnaliser le projet selon nos besoins.

  1. Créez un nouveau projet à l'aide du modèle Cocoa Touch Framework, donnez-lui le nom GifSwift .
  2. Ajoutez les fichiers de bibliothèque libgif que nous avons créés à un groupe distinct dans le projet.
  3. Ajoutez une nouvelle cible pour l'application de test au projet pour voir le résultat.

La structure finale du projet devrait ressembler à ceci:



Importer dans Swift


Afin d'importer une bibliothèque C dans Swift, nous devons la décrire comme un module . La description est un fichier .modulemap contenant une liste de fichiers d'en-tête pour l'importation et des bibliothèques statiques pour la liaison. Le module résultant peut être importé dans du code Swift ou Objective-C (en utilisant @import ).

Cette méthode d'importation de la bibliothèque dans le framework fonctionnera dans la plupart des cas (en savoir plus sur cette approche ici ). Cela fonctionne très bien si vous créez un cadre interne ou si vous divisez simplement votre application en modules. Mais cette méthode présente également des inconvénients. Par exemple, il est inefficace si vous souhaitez transférer votre bibliothèque à quelqu'un utilisant Carthage, Cocoapods ou à cause d'un artefact binaire. La raison en est que le framework résultant n'est généralement pas portable, car lors de la compilation, il est lié à un emplacement spécifique des fichiers d'en-tête et des bibliothèques de la carte du module sur votre ordinateur.

Module explicite


Pour contourner ces limitations, nous utiliserons une autre méthode - le module explicite pour la bibliothèque. Un module explicite est un module qui est déclaré en tant que sous-module à l'aide du mot clé explicite , est placé dans le module parent et n'est pas importé automatiquement. Il fonctionne de manière similaire à *_Private.h pour les *_Private.h Objective-C. Si vous souhaitez utiliser l'API qui y est déclarée, vous devez importer le module explicitement (explicitement).

Nous créons un module explicite pour la bibliothèque C à l'intérieur du framework. Pour ce faire, nous devons redéfinir le module Xcode généré. Notez également que nous ne spécifions pas la bibliothèque libgif.a pour le lien (link gif), mais le faisons directement dans le projet à l'aide de l'interface Xcode.

Remarque: pour en savoir plus sur les modules explicites , cliquez ici.

  1. Ajoutez un fichier appelé GifSwift.modulemap au dossier racine du projet:

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

    Ce fichier contient les spécifications du module CLibgif explicite et se compose d'un fichier d'en-tête déclaré (car il n'y en a qu'un dans notre bibliothèque). Le fichier est chargé dans le module résultant pour le framework.
  2. Le fichier de description du module n'a pas besoin d'être ajouté au framework, mais il doit être spécifié dans les paramètres cibles:

     Build Settings — Packaging — Module Map (MODULEMAP_FILE) = $SRCROOT/GifSwift/GifSwift.modulemap 
  3. Les fichiers libgif doivent être ajoutés à la cible du framework en tant qu'en-tête privé ( gif_lib.h ) et bibliothèque statique ( libgif.a ). Veuillez noter que le fichier d'en-tête de la bibliothèque C a été ajouté à la cible en tant que privé. Ceci est nécessaire pour notre module explicite. Rien n'empêche d'ajouter ce fichier d'en-tête comme public, mais notre tâche consiste à masquer les détails de l'implémentation aussi simple que possible.


  4. Vous pouvez maintenant importer le module explicite à l'intérieur du framework en utilisant import GifSwift.CLibgif

Emballage rapide


Vous pouvez maintenant faire l'interface de notre framework. Une classe suffit, ce qui est un gif avec quelques propriétés:

 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 des interfaces de programmation de bas niveau pour le traitement des fichiers et accède à certaines propriétés, en les mappant à des types Foundation plus pratiques.

Vérifier


Afin de tester notre bibliothèque, j'ai ajouté le fichier cat.gif au projet:

 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")       }   } } 

Lorsque nous exécutons ce code dans la console, nous verrons ce qui suit:

"L' image a une taille: (250,0, 208,0) et contient 44 images"

Conclusions


Le framework résultant contient tout ce dont vous avez besoin pour utiliser, a une interface Swift et, par défaut, cache le code C des clients. Cependant, ce n'est pas entièrement vrai. Comme je l'ai écrit ci-dessus, l'importation de GifSwift.CLibgif vous donnera accès à tous les modules privés, cependant, par défaut, cette méthode d'encapsulation suffit à masquer les détails de la mise en œuvre du framework.

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


All Articles