Building universal static libraries for iOS
I am currently playing around with the ZeroMQ messaging framework and iOS. This article describes the challenges encountered when integrating ZeroMQ with my iOS project and how I mitigated them. This resulted in a tool to automate producing a single libzmq static library for i386, armv7 and armv7s architectures.
For the uninitiated, ZeroMQ provides a simple way to pass messages between processes, whether they are local or remote. It can use TCP, UDP, inproc, multicast and several other protocols as the transport mechanism. In the past I have heard ZeroMQ (or ØMQ from now on) referred to as a “sockets framework”. Although this doesn’t capture the entirety of what ØMQ is, it does provide a nice metaphor for my particular use case.
The project that required ØMQ in this instance was a proof of concept for another project I am currently engaged in. The application in question requires two iOS devices to communicate with each other in realtime across a network. ØMQ provides the communication layer, coupled with Apple’s Bonjour zero config network discovery protocol. As ØMQ is a C based library, integrating it with an iOS project should require relatively little effort. Obtain the libzmq source, compile and add to the Xcode project. But as it turned out, producing a static library for iOS development took some additional effort.
Because Apple provide an iOS simulator for development rather than a more useful iOS emulator, any static library must be compiled for the simulator and then again for iOS devices. When an iOS application runs in the simulator, it is using the x86 (or i386) instruction set; iOS applications are not 64-bit, yet. However all iOS devices use an ARM based CPU of some description, requiring different instructions from their x86 cousins. Apple currently support two ARM architectures, named armv7 for iPhone 4/4S plus the iPad 2 and 3. And armv7s for iPhone 5 and the new iPad 4 (retina display)[1] [2]. Due to this, my project requires ØMQ support for the i386, armv7 and armv7s architecture. It is possible to compile the ØMQ static library three times, add them all to the project and only link to the correct version at compile time. But this can be fiddly, especially when dealing with two almost identical architectures such as armv7 and armv7s.
Fortunately Apple have encountered this multiple architecture problem in their recent past, when moving from the PowerPC to the current x86/x86_64 architecture. Apple wanted to ensure the transitional period was as smooth as possible for their users. So they created the concept of the universal binary, which they imaginatively named a “Universal Application”. To the end user, they could execute the same application on their existing PowerPC powered iMac G5 or a shiny new Intel powered MacBook Pro without issue. However, this apparently magical execution of the same code on two different CPU architectures was nothing more than a cheap parlour trick. In reality a universal binary is a single resource that encapsulates the compiled code for two or more CPU architectures. Although the PowerPC to Intel transition was completed many years ago, the tools to produce these universal binaries remain. Only today iOS developers are combining x86 with ARM instructions.
It is worthwhile to note that universal libraries are also referred to as “fat” libraries, because they are larger than their single architecture counterparts. The overall iOS application size is a factor in how they can be downloaded to the device. Applications weighing in at over 20 megabytes will require a wifi connection to be downloaded, and all applications must be less than two gigabytes. Although the two gigabyte ceiling is unlikely to be a problem due to a fat library, the 20 megabyte limit could cause a concern. If size is a factor for your application then it is probably worthwhile to only include the correct library for the target architecture. I’ll touch on this briefly a bit later.
The ØMQ page describing how to build libzmq for iOS details the cross-compiling process. This is fine for creating an armv7 static library from an Intel powered Mac. But as discussed earlier, this library would not be usable by the iOS simulator. I require a universal static library for libzmq that can execute on all of the required architectures.
The process of creating a universal static library for libzmq is conceptually straight forward, but as it turns out it is quite cumbersome. In theory, all that is required is to compile the libzmq library for i386, armv7 and armv7s and then glue them together into the universal binary. Compiling the binary for ARM requires cross-compiling using a version of GCC with instructions for that CPU. All Mac systems with Xcode installed contain the ARM version of the compiler. The only challenge is figuring out the correct compile flags to set to ensure the right binary is produced.
To save a lot of time I automated the process into a single bash script. I have placed this script on Github for anyone who requires it; available from https://github.com/samsoir/libzmq-ios-universal. It is licensed under the open source ISC license, so modifications and extensions are welcome. For those not willing to look at the source, the script performs the following operations.
Compiles a libzmq static library for the armv7, armv7s and i386 architectures[3].
A tool called lipo is used to perform the gluing of the three libraries into a single universal static library. The header files for the library are copied into the universal folder. Finally the script tidies up after itself and then provides instructions on what to do next. Once the script has finished executing successfully, there will be four versions of the libzmq library. One version for each architecture, and of course the universal version. The individual architecture versions of libzmq remain so that you can use them in place of the universal version if application size is an issue.
The current project on Github is work in progress. It mostly supports my specific requirements at this time. If you try it and it does not work as designed for you, please do tell me or create a pull request. I do intend to add features and more support over time.