CocoaPods: Package Management for OSX and iOS Projects
One of the great things about this OSX and iOS ecosystem is all of the modules that one can find to help speed your development. Of course one of the bad thing is all the modules you can find and trying to manage all the complexities that come with trying to integrate 3rd party modules into your development workflow. There’s lots to do and think about; a small sampler of issues include:
- Do you download the package and copy its source files into you project? Install it a a git module? (What if you project isn’t using git, or the module you want isn’t?)
- How do you manage updates to 3rd party modules when they get updated? How do you freeze a module to a particular version so you’re minimizing moving targets?
- What about ARC?
- How do you manage linker flags, header/library paths
- What if a package you want needs a supporting library that’s at a different version than needed by another package? (This is a very common occurrence with various SDKs that depend on SBJSON — for example Facebook-connect versus almost anything else using SBJSON…)
Is there any way out of this module mayhem? Well, Cocoapods is an interesting open source project that does solve a lot of these us problems in a pretty supportable fashion. CocoaPods is a Ruby “gem” that you install on your Mac that uses a Ruby language “Podfile” in your project’s top-level directory that describes the modules you’d like to include in your project along with any particulars (such as version tags) that can be used as constraints.
The Podile is very simple:
platform :ios dependency 'AFNetworking' dependency 'SBJson' dependency 'SVProgressHUD'
Once you set up this file, you simply invoke “pods install” with your Xcode project name, and the Cocapods installer pulls down the requested packages and builds an Xcode Workspace which contains your application target as well as special “Pods” target that is added as a dependency to your project. This Pods target contains all of the modules you’ve asked for as well as any of their dependencies as a bundle up as a separate library; this allows you to have all these external deps in your project, but away from the main target(s) of your code. It also isolates all of the issues around setting up header and library search paths, dealing with ARC versus MMR (Manual Retain/Release) compilation, etc.
Trying it out
Setting up cocopods is easy and involves one command:
sudo gem install cocoapods
This will cause your Mac to ask for your password (or the username/password of someone with admin privs on your machine); the gem program will then proceed to install the Cocoapods package and all of the packages required to make it run. It’s a very small package, and even with its own dependencies won’t more than a minute or two install.
Once Cocopods itself is installed, pick an existing project or make a small test project to play around with, and create a file named ”Podfile” at the root of your project. To keep things simple we’re going to use our exmple Podfile above, this time with a few additions:
platform :ios dependency 'AFNetworking' dependency 'SBJson', '>= 0.5.1' # for a target where we want to explicity list deps for *only* this target: target :test do dependency 'OCMock' dependency 'LRMocky' dependency 'OCHamcrest' dependency 'Kiwi' end dependency 'SVProgressHUD'
Notice the addition to this version of the file — the part with “0.5.1″ — this is the Cocopaods way of telling the system that you want SBJson version 0.5.1 or later. You can also lock in a specific version by simply using “=” . By default Cocoapods will use whatever version is specified in the PodSpec file, though that may itself default to the “master” branch’s “HEAD” to use the git-speak for the latest code that was checked in. Also, it’s not clear from the Podfile doc what happens if you set up a scenario where there’s an explicit conflict between a module whose version you specify and another module for which your first module is a dependency.
In addition, you can also add, as in the last stanza above. a dependencies for a specific target in your project — in this case the “test” target, where you want to use some testing tools that you would only use as part of your unit and behavioral tests, and never ship compiled into the app you’re going to upload to iTunes Connect.
Once you have saved your Podfile, you initialize Cocoapods by invoking the system with
pod install yourpoject.xcodeproj
Cocoapods is a very lightweight system for abstracting away a lot of annoying configuration issues involved in using 3rd party code in your apps. It’s a lot simpler than learning the ins and outs of git submodules (a very good, but really obscure feature of git), and if the modules you want are already in the Cocoapods collection of PodSpecs then you’re probably all set.
The most obvious “bad” thing one can say is that there are not a lot of 3rd party modules present (as of this writing, 259) in the Cocoapods library, though there are some really nice and useful libraries there (like AFNetworking, Sam Soffes’ SSToolKit, SBJson SVProgressHUD to name just a few); there are of course hundreds more that are not. This will leave you partially in the same place you started, but now with 2 collections of modules to manage if the modules you want aren’t yet there (although you can easily write a “PodSpec” file and contribute it).
Another issue I alluded to is the versioning problem – what happens when you have modules whose own dependencies clash? Its not clear from the documentation if Cocopods “knows” how to resolve these issues, or if you have to use some of the structural features of the Podfile itself to create another target inside the main podfile target that somehow encapsulates the conflicting modules.
For many, people the issue of which version control system to use has been settled by the popularity of github, but for others, who may not be using git, the fact that Cocopods is very git-focused could be an issue. Importing things is obviously not a problem and Cocoapods does have basic support for Mercurial (hg), but adding pods that are based on SVN, perforce or other non-git/hg SCMs might represent a structural problem and introduce yet another meta-dependency into your workflow.
A final tidbit to be aware of is that you will need to add .gitingore (or the equivalent) to the Cocoapods managed modules at the top level or the downloaded pods themselves will be added back into your own projects source code control — and that kinda conflicts with the while purpose of making things modular in the first place.
As we’ve often said here, what makes the iOS and OSX indie dev communities so wonderful is the spirit of sharing and open-ness. Of course in any endeavor there’s a balance between doing everything yourself — from scratch — and finding what’s useful to you and saves you time, but still enables you to develop code that will, to paraphrase Steve Jobs, ‘amaze and delight’ your customers.
Cocoapods can be very helpful, but you can and will find that not every open source or available module is available as a cocoapods distribution — tho’ they do gratefully accept contributions from everyone in terms of pod config (“PodSpec“) files, even if you are not the author of the package.
Also, don’t get lulled into ‘cargo-cult’ thinking that everything you do is just an assemblage of other people’s components, and all you need to to is stich a bunch of other people’s stuff together to make an app. Software is a craft and there is a lot to be said for diving deep, both into a project and into your own coding skills to make something that is truly wonderful. It may sound trite, but the journey really is its own reward – use what works but make sure they what you build it really your own.
There’s also a lot to be said for owning the top-to-bottom structure of your project and knowing how to create modules, sub-projects, target dependencies, etc. — even if you do use some the great modules available out there. Knowing down at the most basic level not just how your code works, but how Xcode and the rest of the Apple tool-chain come together to make your app will serve you well when you run into the inevitable development roadblock where something just isn’t working right.