I had already delivered a SDK, written in Objective-C, as a Static library and its accompanying header files. After a few months, though, the developers who use the SDK came with the reasonable request to be able to integrate it in their apps using these two popular dependency managers.
Enabling bitcode
The developers also requested that Bitcode was enabled. Apple still does not make it mandatory to submit to the App store, but both Cocoapods and Carthage set the “Bitcode enabled” build flag to YES, so my static lib had to include the Bitcode sections.
Build a universal library
If you’ve ever built a “universal” static lib yourself — one that works both on iOS and iOS Simulator targets — then you know that building one consists in:
- build the iOS target
- build the iOS Simulator target
- combine them in a single library using the
lipo
command.
Generate the Bitcode
This article explains that when building the two targets, the OTHER_CFLAGS="-fembed-bitcode"
must be passed to xcodebuild
. And that’s all that is needed.
Surprisingly to me, Bitcode is not an other architecture, but is a section for each architecture instead, marked as __LLVM, __bitcode
in the .a file.
To check that the generation was correct, I searched these sections using MachOView, but you may also use otool and grep.
Carthage
To my surprise, this was the longest part. This is actually not difficult, but there is a catch.
- In Xcode, create a new project, of type “Cocoa Touch Framework”. This is a Dynamic framework.
- Put the .a and the .h files in it.
- Edit the Scheme, and set it as Shared. Otherwise, Carthage won’t be able to build it.
Now the catch: when building the final dynamic framework, Carthage first checks out the framework to the Carthage/Checkouts folder. The important information here is that it does a git checkout
; not a copy!
This is implicit but it was not obvious to me. I wondered why my Objective-C symbols did not show in the final framework, while the reason was simply that my changes to the local directory were not taken into account.
One last point: in the Cartfile, it is possible to provide the name of a git branch instead of a version:
git "path/to/the/local/git/directory" "master"
This is handy since you don’t need to create a new git tag for each change for it to be taken into account. You still need to commit though.
Finally, in my own repository, I added a Carthage directory which contains the Xcode project for the Dynamic framework. I was not able to add the Framework target to the other Xcode project, because of name conflicts.
Cocoapods
Cocoapods deserves its reputation of being complicated and invasive. After reading its documentation again and again, I did what I should have done earlier: find a similar Pod (distributed as a compiled .a) and do the same.
My repo has the following structure:
/Example
/MySDKExampleApp
MySDKExampleApp.xcodeproject
MySDKExampleApp.xcworkspace
Podfile
Podfile.lock
/Pods
README.md
/src
MySDK.h
libMySDK.a
The Podspec:
Pod::Spec.new do |s|
s.name = 'MySDK'
s.version = '1.3.0'
s.summary = 'SDK for MyClient services.'
s.description = <<-DESC
MySDK does lots of things.
DESC
s.homepage = 'https://github.com/MyClient/MySDK'
s.license = { :type => 'Custom', :file => 'README.md' }
s.author = { 'Renaud Pradenc' => 'renaud@mywebsite.com' }
s.source = { :git => 'https://github.com/renaud@myclient.com/MySDK.git', :tag => s.version.to_s }
s.ios.deployment_target = '8.0'
s.source_files = 'src/*.{h,m}'
s.vendored_libraries = 'src/*.a'
s.frameworks = 'UIKit'
s.requires_arc = true
end
Private Podspec
My client does not want anyone to download its SDK, so it is stored in a private github repo, only accessible to a chosen few.
To use the private podspecs, clients only need to write in their Podfile:
source 'https://github.com/MyClient/Podspecs.git'
The private Podspecs repository itself must be organised as follows:
/Specs
/MySDK
/1.3.0
MySDK.podspec
Finishing words
Nothing was particularly difficult, but it took me a lot of time, because of the lack of information, and sometimes contradictions, particularly for Cocoapods (lot of steps described in the doc are not really needed). Carthage proved to be simple enough but has its own logic.