Wie binde ich die C-Bibliothek in das Swift-Framework ein?

Ursprünglicher Autor: Sergey Lem
  • Übersetzung


Im Jahr 2014 wurde Swift eingeführt, eine neue Sprache für die Entwicklung von Ökosystemanwendungen von Apple. Die Neuheit brachte nicht nur neue Funktionen und Funktionen, sondern auch Probleme - für diejenigen, die die guten alten C-Bibliotheken nutzen wollten. In diesem Artikel werde ich einen von ihnen betrachten - das C-Library-Banding in einem Swift-Framework. Es gibt mehrere Wege, um das Problem zu lösen. In diesem Fall erkläre ich, wie dies mit Hilfe von expliziten Modulen von clang geschieht.

Zum Beispiel nehmen wir die externe C-Bibliothek libgif und binden sie in unser Swift-Framework GifSwift ein. Wenn Sie das Ergebnis sofort sehen möchten, können Sie hier das vollständige Projekt sehen .

Libgif-Vorbereitung


Bevor die libgif-Bibliothek in unser Projekt eingebettet wird, sollte sie aus Quellen zusammengestellt werden.

  1. Laden Sie die neueste Version von Tarball hier herunter .
  2. Entpacken Sie das Archiv, wechseln Sie in den Ordner mit der Konsole und führen Sie Folgendes aus:

    ./configure && make check

    Hinweis: Der Einfachheit halber bauen wir eine Bibliothek für die x86-64-Plattform. Daher funktioniert sie nur in einem iOS-Simulator oder auf macOS. Das Erstellen einer statischen Bibliothek mit mehreren Architekturen ist ein separates Thema, das ich in diesem Artikel nicht ansprechen möchte. Nützliche Anweisungen finden Sie hier .
  3. Wenn alles reibungslos läuft, finden Sie die Bibliotheksdateien in ${lib_gif_source}/lib/.libs. Wir sind an zwei Dateien interessiert:

    lib/.libs/libgif.a # Статическая библиотека
    lib/gif_lib.h # Интерфейс
    

Projekteinrichtung


Jetzt passen wir das Projekt an unsere Bedürfnisse an.

  1. Erstellen Sie ein neues Projekt mit der Vorlage Cocoa Touch Framework und geben Sie ihm den Namen GifSwift .
  2. Fügen Sie die von uns erstellten libgif- Bibliotheksdateien einer separaten Gruppe innerhalb des Projekts hinzu.
  3. Fügen Sie dem Projekt ein neues Ziel für eine Testanwendung hinzu, um das Ergebnis anzuzeigen.

Die endgültige Projektstruktur sollte folgendermaßen aussehen:



Importieren Sie schnell


Um die C-Bibliothek in Swift zu importieren, müssen wir sie als Modul beschreiben . Die Beschreibung ist eine .modulemap- Datei mit einer Liste von Header-Dateien für den Import und statischen Bibliotheken für die Verknüpfung. Das resultierende Modul kann in Swift- oder Objective-C-Code (mit @import) importiert werden .

Diese Art des Importierens der Bibliothek in das Framework funktioniert in den meisten Fällen (weitere Informationen zu diesem Ansatz finden Sie hier.)). Es ist großartig, wenn Sie ein internes Framework erstellen oder Ihre Anwendung einfach in Module zerlegen. Diese Methode hat aber auch Nachteile. Beispielsweise ist es unwirksam, wenn Sie Ihre Bibliothek an jemanden übertragen möchten, der Carthage, Cocoapods oder ein binäres Artefakt verwendet. Der Grund ist, dass das resultierende Framework im Allgemeinen nicht portierbar ist, da es beim Kompilieren an einen bestimmten Speicherort der Header-Dateien und Bibliotheken aus der Modulzuordnung auf Ihrem Computer gebunden ist.

Explizites Modul


Um diese Einschränkungen zu umgehen, verwenden wir einen anderen Weg - das explizite Modul für die Bibliothek. Ein explizites Modul ist ein Modul, das mit dem expliziten Schlüsselwort zu einem Untermodul deklariert , im übergeordneten Modul abgelegt und nicht automatisch importiert wird. *_Private.hBei Objective-C-Frameworks funktioniert es ähnlich . Wenn Sie die darin deklarierten APIs verwenden möchten, müssen Sie das Modul explizit importieren .

Wir erstellen ein explizites Modul für die C-Bibliothek innerhalb des Frameworks. Dazu müssen wir das generierte XCode-Modul neu definieren. Beachten Sie auch, dass wir die Bibliothek libgif.a nicht zum Verknüpfen angeben (link gif), sondern sie im Projekt mithilfe der XCode-Schnittstelle direkt ausführen.

Hinweis: Um mehr über explizite Module zu erfahren, klicken Sie bitte hier.

  1. Fügen Sie dem Stammordner des Projekts eine Datei mit dem Namen GifSwift.modulemap hinzu :

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

    Diese Datei enthält die Spezifikation für das explizite CLibgif- Modul und besteht aus einer deklarierten Header-Datei (da es nur eine solche in unserer Bibliothek gibt). Die Datei wird in das resultierende Modul für das Framework geladen.
  2. Die Datei mit der Beschreibung des Moduls muss nicht zum Framework hinzugefügt werden, sondern muss in den Einstellungen des Ziels angegeben werden:

    Build Settings — Packaging — Module Map (MODULEMAP_FILE)
    =
    $SRCROOT/GifSwift/GifSwift.modulemap
  3. Die libgif-Dateien müssen in Form eines privaten Headers ( gif_lib.h ) und einer statischen Bibliothek ( libgif.a ) zum Zielframework hinzugefügt werden . Beachten Sie, dass die Header-Datei für die C-Bibliothek als privat zum Ziel hinzugefügt wird. Dies ist für unser explizites Modul erforderlich. Es gibt nichts dagegen, diese Header-Datei als öffentlich hinzuzufügen, aber unsere Aufgabe besteht darin, Implementierungsdetails mit möglichst einfachen Mitteln auszublenden.


  4. Jetzt können Sie mit ein explizites Modul innerhalb des Frameworks importieren import GifSwift.CLibgif

Schneller Wrapper


Jetzt können Sie die Schnittstelle unseres Frameworks erstellen. Eine Klasse ist genug, um ein GIF mit ein paar Eigenschaften zu haben:

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 umschließt Low-Level-APIs für die Dateiverarbeitung, erhält Zugriff auf einige Eigenschaften und ordnet sie den bequemeren Foundation-Typen zu.

Überprüfen


Um unsere Bibliothek zu testen, habe ich die Datei cat.gif zum Projekt hinzugefügt :

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

Wenn Sie diesen Code in der Konsole ausführen, wird Folgendes angezeigt :

" Bild hat Größe: (250.0, 208.0) und enthält 44 Bilder"

Schlussfolgerungen


Das resultierende Framework enthält alles, was Sie verwenden müssen, verfügt über eine Swift-Schnittstelle und verbirgt standardmäßig den C-Code vor Clients. Dies ist jedoch nicht ganz richtig. Wie ich bereits beim Importieren von GifSwift.CLibgif geschrieben habe , erhalten Sie Zugriff auf alle geschlossenen Module. Diese Methode der Kapselung reicht jedoch standardmäßig aus, um die Implementierungsdetails des Frameworks auszublenden.

Jetzt auch beliebt: