如何在Swift框架中嵌入C库



2014年,Swift被引入,这是一种用于开发Apple生态系统应用程序的新语言。 对于那些想使用旧的C库的人来说,新颖性不仅带来了新的特性和功能,还带来了问题。 在本文中,我将考虑其中之一-Swift框架中的C库捆绑。 有几种解决方法。 在这种情况下,我将说明如何使用clang显式模块执行此操作。

例如,我们采用外部libgif C库并将其嵌入到我们的Swift GifSwift框架中。 如果要立即查看结果,可以在此处查看整个项目。

准备libgif


在将libgif库嵌入我们的项目之前,您需要从源代码进行编译。

  1. 此处下载最新的tarball。
  2. 使用控制台解压缩存档,转到文件夹并运行:

    ./configure && make check 

    注意:为简单起见,我们正在为x86-64平台编译一个库,因此该库仅可在iOS模拟器或macOS上运行。 构建多体系结构静态库是一个单独的主题,在本文中我不会涉及。 有用的说明可以在这里找到。
  3. 如果一切顺利,则可以在${lib_gif_source}/lib/.libs找到库文件。 我们对两个文件感兴趣:

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

项目设置


现在,我们将根据需要定制项目。

  1. 使用Cocoa Touch Framework模板创建一个新项目,并将其命名为GifSwift
  2. 将我们创建的libgif库文件添加到项目中的单独组中。
  3. 将测试应用程序的新目标添加到项目中以查看结果。

项目的最终结构应如下所示:



导入到Swift


为了将C库导入Swift,我们必须将其描述为一个模块 。 描述是一个.modulemap文件,其中包含用于导入的头文件列表和用于链接的静态库列表。 可以将生成的模块导入到Swift或Objective-C代码中(使用@import )。

这种将库导入框架的方法在大多数情况下都可以使用( 在此处了解有关此方法的更多信息)。 如果您正在创建内部框架或只是将应用程序分解为模块,那么它会非常有用。 但是这种方法也有缺点。 例如,如果您想将库转移给使用迦太基,可可足类的人或由于二进制工件而无效。 原因是所生成的框架通常不可移植,因为编译时它会绑定到计算机上模块映射中的头文件和库的特定位置。

显式模块


为了克服这些限制,我们将使用另一种方法-库的显式模块。 显式模块是使用explicit关键字声明为子模块的模块,位于父模块中, 不会自动导入 。 它与用于Objective-C框架的*_Private.h类似。 如果要使用其中声明的API,则必须显式(显式)导入模块

我们正在为框架内的C库创建一个显式模块。 为此,我们需要重新定义生成的Xcode模块。 另外,请注意,我们没有为链接(link gif)指定libgif.a库,而是使用Xcode接口直接在项目中进行设置。

注意:要了解有关显式模块的更多信息,请单击此处。

  1. 将一个名为GifSwift.modulemap的文件添加到项目的根文件夹:

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

    该文件包含显式CLibgif模块的规范,并包含一个声明的头文件(因为我们的库中只有一个)。 该文件被加载到框架的结果模块中。
  2. 模块描述文件不需要添加到框架,但是必须在目标设置中指定:

     Build Settings — Packaging — Module Map (MODULEMAP_FILE) = $SRCROOT/GifSwift/GifSwift.modulemap 
  3. libgif文件应作为专用标头( gif_lib.h )和静态库( libgif.a )添加到框架目标。 请注意,C库的头文件已作为私有文件添加到目标中。 这对于我们的显式模块是必需的。 没有什么可以阻止将此头文件公开添加的,但是我们的任务是尽可能简单地隐藏实现细节。


  4. 现在,您可以使用import GifSwift.CLibgif将显式模块import GifSwift.CLibgif

迅捷包装


现在,您可以执行我们框架的接口。 一个类就足够了,这是一个具有两个属性的gif:

 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包装了用于处理文件的低级编程接口,并访问了一些属性,将它们映射到更方便的Foundation类型。

检查一下


为了测试我们的库,我在项目中添加了cat.gif文件:

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

在控制台中运行此代码时,将看到以下内容:

图片的大小:(250.0,208.0),包含44张图片”

结论


生成的框架包含您需要使用的所有内容,具有Swift界面,并且默认情况下对客户端隐藏C代码。 但是,这并非完全正确。 如我上面所述,导入GifSwift.CLibgif可以访问所有私有模块,但是,默认情况下,此封装方法足以隐藏框架实现的细节。

Source: https://habr.com/ru/post/zh-CN435650/


All Articles