Thursday, July 27, 2017

[iOS] - Static Libraries, Frameworks, and Bitcode

A couple weeks ago, I had to add Bitcode support for an Objective-C library I was working on. My first thought was something along the lines of:
I just have to change `Enable Bitcode` to YES on my Project Settings, awesome!
Wrong.

Ways to use libraries

Libraries on Objective-C/Swift can generally be used in 3 different ways:
  1. Binary
  2. CocoaPods
  3. Frameworks / Carthage
In my case, the library could be used via CocoaPods or a Universal Binary. The universal binary was being built by an Aggregate target, which had a custom Run Script to build a "fat" (simulator+device) version of it. To my surprise, Bitcode was already enabled in the project "Build Settings", but a user was getting errors when using the binary. So, what was going on?

Extra Bitcode settings

Well, turns out one setting wasn't enough. Xcode will only build a project with Bitcode support if you Archive it, which is not usually the case when you're working with libraries. To get around that, you can use either one of two settings on your project (whichever one you prefer):
  • On Build Settings -> Other C flags, set Debug to -fembed-bitcode-marker, and Release to -fembed-bitcode. Make sure that is set for your project, and not the targets. (thanks @naishta for pointing that out!)
  • On Build Settings, click on the + sign at the top to add a user-defined build setting with the name BITCODE_GENERATION_MODE, and set Debug to marker, Release to bitcode
If you're using xcodebuild or xctool to build your project, you can use it like this:
# Debug
xcodebuild … OTHER_CFLAGS=”-fembed-bitcode-marker” …
# Release
xcodebuild … OTHER_CFLAGS=”-fembed-bitcode” …
The marker or -fembed-bitcode-marker option for Bitcode is only useful for testing. You can have an app with Bitcode Enabled, using a binary from a library that was built with the -fembed-bitcode-marker setting, for example, and it'll work fine while you're testing. But if you try to Archive your app, you'll get an error such as this:
ld: bitcode bundle could not be generated because ‘{project dir}/path/to/SomeLib.a’ was built without full bitcode. All object files and libraries for bitcode must be generated from Xcode Archive or Install build for architecture arm64
To fix that, make sure that you're building your library's binary with the option -fembed-bitcode before importing it into an app.
P.S: To test if you're correctly building with full bitcode support, or to check other libraries you might be using, use the `otool` command in your terminal.
Okay, that takes care of the Universal Binary. 🎉

What about CocoaPods and Carthage/Frameworks?

Well, if you're using CocoaPods you might have been lucky and not have
seen any errors regarding Bitcode. Pods will be added to your workspace with Bitcode Enabled set to YES, and hopefully, you'll be good to go without lifting a finger.
Now, our last option, Carthage/Frameworks. The same settings I mentioned above will allow you to build a framework with full Bitcode support (staticor dynamic). But, if you have a setup that creates a "fat" (simulator+device) framework, which is the more common case, there's a bug with the App Store submission process that will throw an error if your app includes a framework with extra architectures. Carthage has instructions for getting around the bug that can be found here, that basically involves adding a few extra steps to your Build Phases, and adding a script that will strip a framework of those extra architectures.
Hopefully, the information here will help you get a little bit less frustrated and save some time searching on Google/StackOverflow when dealing with Bitcode. :)