In 2014, Swift was introduced, a new language for developing Apple's ecosystem applications. The novelty brought not only new features and functions, but also problems - to those who wanted to use the good old C-libraries. In this article I will discuss one of them - the C-library banding in a Swift framework. There are several ways to solve it; in this case, I will explain how to do this with clang explicit modules.
For example, we take the external C library
libgif and embed it in our Swift framework GifSwift. If you want to see the result immediately, you can see the full project
here .
Libgif preparation
Before embedding the libgif library into our project, it should be compiled from sources.
')
- Download the latest version of tarball here .
- Unpack the archive, use the console to go to the folder and run:
./configure && make check
Note: for simplicity, we are building a library for the x86-64 platform, and therefore it will only work in an iOS simulator or on macOS. Building a multi-architecture static library is a separate topic, which I don’t touch on in this article. Useful instructions can be found here .
- If everything goes
${lib_gif_source}/lib/.libs
, the library files can be found in ${lib_gif_source}/lib/.libs
. We are interested in two files:
lib/.libs/libgif.a # lib/gif_lib.h #
Project Setup
Now we will adjust the project to our needs.
- Create a new project using the template Cocoa Touch Framework, give it the name GifSwift .
- Add the libgif library files we created to a separate group within the project.
- Add a new target for the test application to the project to see the result.
The final project structure should look like this:
Import to Swift
In order to import the C library into Swift, we need to describe it as a
module . The description is a
.modulemap file containing a list of header files for import and static libraries for linking. The resulting module can be imported into Swift or Objective-C code (using
@import
).
This method of importing the library into the framework will work in most cases (read more about this approach
here ). It is great if you are creating an internal framework or simply breaking your application into modules. But this method also has disadvantages. For example, it is ineffective if you want to transfer your library to someone using Carthage, Cocoapods or in the sense of a binary artifact. The reason is that the resulting framework is generally not portable, because when compiled it is tied to a specific location of the header files and libraries from the module map on your computer.
Explicit module
To circumvent these limitations, we will use another way - an
explicit module for the library. An explicit-module is a module that is declared a sub-module with the
explicit keyword, is placed in the parent module, and is
not imported automatically. It works like
*_Private.h
for Objective-C frameworks. If you want to use the APIs declared in it, you need to import the module
explicitly.
We create an explicit module for the C-library inside the framework. To do this, we need to perform a redefinition of the generated XCode module. Also note that we do not specify the libgif.a library for linking (link gif), but instead do it right in the project using the Xcode interface.
Note: To learn more about explicit modules, please click here.
- Add a file called GifSwift.modulemap to the root folder of the project:
framework module GifSwift { umbrella header "GifSwift.h" explicit module CLibgif { private header "gif_lib.h" } export * }
This file contains the specification for the explicit CLibgif module and consists of one declared header file (since there is just one such in our library). The file is loaded into the resulting module for the framework.
- The file with the description of the module does not need to be added to the framework, but it must be specified in the settings of the target:
Build Settings — Packaging — Module Map (MODULEMAP_FILE) = $SRCROOT/GifSwift/GifSwift.modulemap
- The libgif files must be added to the target framework in the form of a private header ( gif_lib.h ) and a static library ( libgif.a ). Note that the header file for the C library is added to the target as private. This is necessary for our explicit module. Nothing prevents to add this header file as public, but our task is to hide the implementation details with the simplest possible means.
- Now you can import an explicit module inside the framework using
import GifSwift.CLibgif
Swift-wrapper
Now you can do the interface of our framework. There is enough one class, which is a gif with a couple of properties:
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
wraps low-level APIs for file processing and gains access to some properties, mapping them to more convenient Foundation types.
Check
In order to test our library, I added the file
cat.gif to the project:
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") } } }
When running this code in the console, we will see the following:
" Image has size: (250.0, 208.0) and contains 44 images"
findings
The resulting framework contains everything you need to use, has a Swift interface, and by default hides the C code from clients. However, this is not entirely true. As I wrote above, importing
GifSwift.CLibgif , you get access to all closed modules, but by default this method of encapsulation is enough to hide the implementation details of the framework.