Red light

CocoaPods for Device-Only iOS Libraries

August Jaenicke ·

Here at Carbon Five, we consider testing the software we write to be crucial to the long term stability and velocity of our projects. We also value developer productivity. The iOS simulator is a very valuable tool for testing and development. Recently a major upgrade of a library prevented an iOS project from running and testing in the iOS simulator. We had a problem. A little bit of refactoring and a new CocoaPods podspec got things moving again.

The Problem – I can’t run or test my iOS app in the simulator anymore….

Don’t expect every iOS library to support the simulator.

TokBox’s original OpenTok iOS library supported limited functionality in the iOS simulator. The support was more than enough to accomplish most development and testing. The new OpenTok WebRTC framework can only link against the ARM architectures, and not the i386 architecture needed to run the simulator. I’m sure the TokBox engineers are working hard on optimizing the experience on real devices using all the horsepower available; I doubt it is realistic to expect them to write a second version of their client just for the simulator. Nevertheless, I considered giving up the simulator unacceptable; TDD and overall development is much faster with the simulator. Also, abandoning the simulator would limit our continuous integration options.

The Solution – Stub out the library

Realizing that you don’t need everything all the time will set you free.

I was willing to accept running the simulator without any of the OpenTok functionality since most of the complexity and functionality are not associated with OpenTok. We also didn’t need an actual OpenTok session for any of our tests. I abstracted out a service protocol that represented the app’s interaction with OpenTok. To run or test in the simulator, the app loads up a fake implementation of the new OpenTok service protocol. I typically mock out service objects in unit tests, so this had almost no impact on unit tests. Problem solved – sort of. Xcode still tried to link against the ARM Opentok Library even when targeting the simulator.

Telling Xcode you only want to use a library when running on a real device

Hey, I didn’t know I could do that!
At the time of our OpenTok upgrade, we didn’t have a podspec for the original non-WebRTC OpenTok iOS library. I had never written a podspec before, and the discomfort of downloading, then manually including and configuring OpenTok had been tolerable. But that changed with the inability to link the OpenTok library against the i386 architecture. I had to wrestle Xcode into only linking OpenTok when targeting ARM. Here was a major pain point; convincing Xcode to do what I want is neither fun nor reliable. CocoaPods automates so much of the hassle around dependencies in a clear, declarative and reproducible way. Now was the time to write my first podspec.

Getting down to business

Create and configure your private CocoaPod Repository

Before you can write your own podspec, you need to create and configure your own private repository. It’s easier than you think. Jeffery Jackson’s excellent article on private cocoapod repositories was all I needed. Don’t forget that each developer will need to add this new repository to their cocoapods configuration.

I’m not going to go over every detail of the podspec. You can find both the individual podspec and the cocoapod repo. If you just want to use it, simply add the following to your Podfile:

pod 'opentok-ios-sdk-webrtc', '2.1.7'

and use the pod command to tell CocoaPods to also look at my repo:

pod repo add Cocoapods_Specs https://github.com/augustj/Cocoapods_Specs.git

 

A new day and a new podspec

Grab the files from git and set the platform to iOS, 6.1. The OpenTok iOS SDK Release Notes indicate that your project must target iOS 6.1. Setting it to 7.x will result in compilation errors.

Pod::Spec.new do |s|
  ...
  s.source = { :git => "https://github.com/opentok/opentok-ios-sdk-webrtc.git", :tag => "2.1.7" }
  s.platform = :ios, '6.1'

TokBox distributes the library as an iOS Framework, complete with compiled code. I extract the pieces needed and put them into the correct place in the Pods workspace.

  s.source_files   = 'Opentok.framework/Versions/A/Headers/*'
  s.preserve_paths = 'Opentok.framework/*'
  s.exclude_files  = 'Opentok.framework/Versions/A/Resources/Info.plist'
  s.resources = ['Opentok.framework/Versions/A/Resources/*'

 

Use OTHER_LDFLAGS[arch=XXX] to control linking in a podspec

Put together a list of the frameworks required and add additional flags per the library instructions. Notice that we are including OpenTok. But then we only add those flags under OTHER_LDFLAGS for arm64, armv7, and armv7s. We will not link to any of those frameworks, including OpenTok, when arch=i386.

  other_frameworks =  ['Opentok', 'AudioToolbox', 'AVFoundation', 'CFNetwork', 
    'CoreAudio', 'CoreMedia', 'CoreTelephony', 'CoreVideo', 'MobileCoreServices', 
    'OpenGLES', 'QuartzCore', 'Security', 'SystemConfiguration']
  
  other_ldflags = '$(inherited) -framework ' + other_frameworks.join(' -framework ') + 
    ' -lz -lstdc++'

  s.xcconfig     = { 
    'FRAMEWORK_SEARCH_PATHS' => '"$(PODS_ROOT)/opentok-ios-sdk-webrtc"',

    'OTHER_LDFLAGS[arch=arm64]'  => other_ldflags,
    'OTHER_LDFLAGS[arch=armv7]'  => other_ldflags,
    'OTHER_LDFLAGS[arch=armv7s]' => other_ldflags
  }
end

 

Load the code appropriate for the current target

The final task is to make sure the app loads the fake service implementation for the simulator, and the real implementation for devices. I use Objection, a great framework modeled after Guice, to wire up my service objects. I create a protocol for an OpenTok service, then two implementations. The first is the real version that wraps the OpenTok library. The second is a fake version that has dummy calls (this app could still function just fine without the video). Depending on the target, Objection will wire up the appropriate version.

#if TARGET_IPHONE_SIMULATOR
    [self registerEagerSingleton:[AJFakeOpenTokService class]];
    [self bindClass:[AJFakeOpenTokService class] toProtocol:@protocol(AJOpenTokService)];
#else
    [self registerEagerSingleton:[AJRealOpenTokService class]];
    [self bindClass:[AJRealOpenTokService class] toProtocol:@protocol(AJOpenTokService)];
#endif

 

Conclusions

Writing custom CocoaPods spec files is not difficult and offers many benefits. Podspecs enable advanced configuration of dependencies with a simple, declarative format. They can reference code and binaries in a framework on GitHub or a zip file on S3. A custom podspec will provide as much functionality as a manually configured dependency in Xcode. Changes to podspecs are much easier to track over time than Xcode settings for a manually configured dependency. Would you rather diff a 40 line podspec file or a Xcode workspace file 1000’s of lines long? You can also create podspecs that use only part of a framework or SDK. I used a custom podspec to pull in only S3-related files from Amazon’s very comprehensive and large AWS SDK. Capturing only the files needed for S3 kept the project much smaller in terms of files, build time and final app size. When it is time to upgrade the dependency, just create a new version of the podspec. Once you write your first custom podspec, you will never want to drag a dependency into Xcode again.