Bundling C library into a Swift framework

lem
Bumble Tech
Published in
4 min readDec 12, 2018

--

In 2014 Apple introduced Swift, a new language for developing applications across its ecosystem. As well as offering various new possibilities and features, it also created some new challenges for those wanting to use good old C libraries. In this article, I will describe one common use case which you may encounter — introducing C library into a Swift framework. There are several ways to achieve this and I will explain how to do this using explicit module maps.

In my example, I will consider a simple use case of an external C library giflib which I will use from Swift framework GifSwift.

For those who want to get straight to the point, click here to see the complete project:

https://github.com/turbulem/GifSwift

Baking libgif

Before embedding library to our project, we need to build it from sources:

  1. Download the latest version of tarball here
  2. Unpack it, go to the folder using Terminal and run
./configure && make check

Note: for simplicity, we are only building the library for x86_64 platform so it will only work in iOS simulator or macOS. Making a multi-architectural static library is the whole different topic and is not covered in this post. You can find some useful instructions here: “A noob’s guide to creating a ‘Fat Library’ for iOS”

3. If everything goes well we will be able to find resulting static library files in ${lib_gif_source}/lib/.libs

The only two files we need are:

lib/.libs/libgif.a # Static library archive
lib/gif_lib.h # Header interface

Project setup

Now let’s set up a basic project that suits our needs.

  1. Firstly, create a new project using template “Cocoa Touch Framework” with the name like GifSwift
  2. Then add previously created libgif library files into the separate group under framework target folder
  3. Finally, add an application target to the project in order to be able to test the library.

In the end, the project structure that we end up with should look something like this:

Project structure with the static library files

Import To Swift

In order to import C library into Swift, it first need to be be declared as a module. Declaration takes the form of a module map — a file describing headers to import and static libraries to link with. The resulting module may be imported into Swift (natively) and Objective-C (using @import keyword).

This way of embedding C library into a framework will work for you in most cases (learn more about this approach here and there). It is totally legit as long as you’re developing some internal framework or simply modularising your application into separate parts. However, it doesn’t work well when you need to ship your library to external users via Carthage, Cocoapods or as a binary. The main reason being that the resulting framework is not portable between computers. When undergoing compilation, your resulting framework is bound to the current location of headers and libraries from module map on your computer. You can use it right away in your project but should you try to send it to somebody else they will not be able to link it to a project because files referenced by module map are no longer accessible.

Explicit module

We are going to use a different approach for making the module map — Explicit module. An explicit module is the one declared as submodule inside a parent using the special keyword explicit and is not imported with it automatically. It works like *_Private.h header for an Objective-C framework. If you want to use APIs declared in it, you need to import it explicitly. We are going to create an explicit module for a C-library inside framework. To do this, we need to override Xcode-generated module map.

Note: more about explicit modules you can read at Clang Modules Reference

  1. Let’s add a new file to the root of our project structure with the name GiftSwift.modulemap

This file contains a specification for explicit module CLibGif and consists of only of one header declaration (because our selected library contains only one header) and is “embedded” into the resulting module for a framework.

2. This file should not be added to any target and must be specified in

Build Settings — Packaging — Module Map (MODULEMAP_FILE)=$SRCROOT/GifSwift/GifSwift.modulemap

3. libgif files must be added to framework target as Private Header (gif_lib.h) and linked static library (libgif.a)

Framework Build Phases tab after correct setup

4. At this point we can import our explicit module into the framework using:

import GifSwift.CLibgif

Swift Wrapper

Now let’s make an interface for our framework in Swift. We will use just one class, representing gif image file with a couple of properties:

GifFile class

GifFile.swift wraps low-level C APIs for file handle management and accesses some properties, mapping them into Foundation types.

Using framework

In order to test how our library works, I added a cat.gif image to the bundle and some code to check it using the framework.

Running this code prints the following line in console:

“Image has size: (250.0, 208.0) and contains 44 images”

Summary

The resulting framework is portable, has a pure-swift interface and doesn’t expose any C code to external clients. Actually, this is not entirely true 🙂. As I mentioned above, you can import GifSwift.CLibgif from the outside and have access to all the private modules, potentially altering some of its global shared state but, by default, this method of encapsulation is just enough to hide all the implementation details of the framework.

--

--