CocoaPods is a library dependency management tool for OS X and iOS applications. With CocoaPods, you can define your dependencies, called
pods, and manage their versions easily over time and across development environments.
The philosophy behind CocoaPods is twofold. Firstly, including third-party code in your projects involves many hoops. For the beginning Objective-C developer, the project file is daunting. Going through the steps of configuring build phases and linker flags leaves a lot of room for human error. CocoaPods simplifies all of that, and automatically configures your compiler settings.
Secondly, CocoaPods makes it easy to discover new third-party libraries. Now, this doesn't mean you should go and build a FrankenApp, where every part is written by somebody else and simply stitched together. It does mean that you can find really good libraries that shorten your development cycle and improve the quality of your software.
In this article, we will walk through the
pod install process, and take a deeper look at what CocoaPods is doing behind the scenes.
CocoaPods is written in Ruby and actually is made of several Ruby Gems. The most important gems when explaining the integration process are CocoaPods/CocoaPods, CocoaPods/Core, and CocoaPods/Xcodeproj (yes, CocoaPods is a dependency manager that is built using a dependency manager!).
This is the user-facing component and is activated whenever you call a
pod command. It includes all the functionality you need to actually use CocoaPods, and makes use of all of the other gems to perform tasks.
The Core gem provides support for working with the files that are involved with CocoaPods, mainly the Podfile and podspecs.
The Podfile is the file that defines the pods you want to use. It is highly customizable, and you can be as specific as you'd like. For more information, check out the Podfile guide.
.podspec is a file that determines how a particular pod is added to a project. It supports features such as listing source files, frameworks, compiler flags, and any other dependencies that a library requires, to name a few.
This gem handles all of the project file interactions. It has the ability to both create and modify
.xcworkspace files. It is also useable as a standalone gem, so if you ever wanted to write scripts and easily modify the project file, this gem is for you.
There is a lot that happens when
pod install runs. The easiest insight into this is running the command with
--verbose. Run that command,
pod install --verbose now, and then come back. It will look something like this:
$ pod install --verbose Analyzing dependencies Updating spec repositories Updating spec repo `master` $ /usr/bin/git pull Already up-to-date. Finding Podfile changes - AFNetworking - HockeySDK Resolving dependencies of `Podfile` Resolving dependencies for target `Pods' (iOS 6.0) - AFNetworking (= 1.2.1) - SDWebImage (= 3.2) - SDWebImage/Core Comparing resolved specification to the sandbox manifest - AFNetworking - HockeySDK Downloading dependencies -> Using AFNetworking (1.2.1) -> Using HockeySDK (3.0.0) - Running pre install hooks - HockeySDK Generating Pods project - Creating Pods project - Adding source files to Pods project - Adding frameworks to Pods project - Adding libraries to Pods project - Adding resources to Pods project - Linking headers - Installing libraries - Installing target `Pods-AFNetworking` iOS 6.0 - Adding Build files - Adding resource bundles to Pods project - Generating public xcconfig file at `Pods/Pods-AFNetworking.xcconfig` - Generating private xcconfig file at `Pods/Pods-AFNetworking-Private.xcconfig` - Generating prefix header at `Pods/Pods-AFNetworking-prefix.pch` - Generating dummy source file at `Pods/Pods-AFNetworking-dummy.m` - Installing target `Pods-HockeySDK` iOS 6.0 - Adding Build files - Adding resource bundles to Pods project - Generating public xcconfig file at `Pods/Pods-HockeySDK.xcconfig` - Generating private xcconfig file at `Pods/Pods-HockeySDK-Private.xcconfig` - Generating prefix header at `Pods/Pods-HockeySDK-prefix.pch` - Generating dummy source file at `Pods/Pods-HockeySDK-dummy.m` - Installing target `Pods` iOS 6.0 - Generating xcconfig file at `Pods/Pods.xcconfig` - Generating target environment header at `Pods/Pods-environment.h` - Generating copy resources script at `Pods/Pods-resources.sh` - Generating acknowledgements at `Pods/Pods-acknowledgements.plist` - Generating acknowledgements at `Pods/Pods-acknowledgements.markdown` - Generating dummy source file at `Pods/Pods-dummy.m` - Running post install hooks - Writing Xcode project file to `Pods/Pods.xcodeproj` - Writing Lockfile in `Podfile.lock` - Writing Manifest in `Pods/Manifest.lock` Integrating client project
There's a lot going on here, but when broken down, it's all very simple. Let's walk through it.
Reading the Podfile
If you've ever wondered why the Podfile syntax looks kind of weird, that's because you are actually writing Ruby. It's just a simpler DSL to use than the other formats available right now.
So, the first step during installation is figuring out what pods are both explicitly or implicitly defined. CocoaPods goes through and makes a list of all of these, and their versions, by loading podspecs. Podspecs are stored locally in
Versioning and Conflicts
CocoaPods uses the conventions established by Semantic Versioning to resolve dependency versions. This makes resolving dependencies much easier, since the conflict resolution system can rely on non-breaking changes between patch versions. Say, for example, that two different pods rely on two versions of CocoaLumberjack. If one relies on
2.3.1 and another
2.3.3, the resolver can use the newer version,
2.3.3, since it should be backward compatible with
But this doesn't always work. There are many libraries that don't use this convention, which makes resolution difficult.
And of course, there will always be some manual resolution of conflicts. If one library depends on CocoaLumberjack
1.2.5 and another
2.3.1, only the end user can resolve that by explicitly setting a version.
The next step in the process is actually loading the sources. Each
.podspec contains a reference to files, normally including a git remote and tag. These are resolved to commit SHAs, which are then stored in
~/Library/Caches/CocoaPods. The files created in these directories are the responsibility of the Core gem.
The source files are then downloaded to the
Pods directory using the information from the
.podspec, and caches.
Generating the Pods.xcodeproj
pod install is run and changes are detected, the
Pods.xcodeproj is updated using the Xcodeproj gem. If the file doesn't exist, it's created with some default settings. Otherwise, the existing settings are loaded into memory.
When CocoaPods adds a library to the project, it adds a lot more than just the source. Since the change to each library getting its own target, for each library, several files are added. Each source needs:
.xcconfigthat contains the build settings
.xcconfigthat merges these build settings with the default CocoaPods configuration
prefix.pchwhich is required for building
dummy.mwhich is also required for building
Once this is done for each pod target, the overall
Pods target is created. This adds the same files, with the addition of a few more. If any source contains a resource bundle, instructions on adding that bundle to your app's target will be added to
Pods-Resources.sh. There's also a
Pods-environment.h, which has some macros for you to check whether or not a component comes from a pod. And lastly, two acknowledgement files are generated, one
markdown, to help end users conform with licensing.
Writing to Disk
Up until now, a lot of this work has been done using objects in memory. In order for this work to be reproducible, we need a file record of all of this. So the
Pods.xcodeproj is written to disk, along with two other very important files,
This is one of the most important files that CocoaPods creates. It keeps track of all of the resolved versions of pods that need to be installed. If you are ever curious as to what version of a pod was installed, check this file. This also helps with consistency across teams if this file is checked in to source control, which is recommended.
This is a copy of the
Podfile.lock that gets created every time you run
pod install. If you've ever seen the error
The sandbox is not in sync with the Podfile.lock, it's because this file is no longer the same as the
Podfile.lock. Since the
Pods directory is not always under version control, this is a way of making sure that developers update their pods before running, as otherwise the app would crash, or the build would fail in another, less visible, way.
If you have xcproj installed on your system, which we recommend, it will
Pods.xcodeproj to turn it into the old ASCII plist format. Why? The writing of those files is no longer supported, and hasn't been for a while, yet Xcode still relies on it. Without xcproj, your
Pods.xcodeproj is written as an XML plist, and when you open it in Xcode, it will be rewritten, causing large file diffs.
The finished product of running
pod install is that a lot of files have been added to your project and created on the system. This process usually only takes a few seconds. And, of course, everything that CocoaPods does can be done without it. But it'll take a lot longer than a few seconds.
Sidenote: Continuous Integration
CocoaPods plays really well with Continuous Integration. And, depending on how you have your project set up, it's still fairly easy to get these projects building.
With a Version-Controlled Pods Folder
If you version control the
Pods folder and everything inside it, you don't need to do anything special for continuous integration. Just make sure to build your
.xcworkspace with the correct scheme selected, as you need to specify a scheme when building with a workspace.
Without a Pods Folder
When you do not version control your
Pods folder, you need to do a few more things to get continuous integration working properly. At the very least, the
Podfile needs to be checked in. It's recommended that the generated
Podfile.lock are also under version control for ease of use, as well as making sure that the correct pod versions are used.
Once you have that setup, the key with running CocoaPods on CI is making sure
pod install is run before every build. On most systems, like Jenkins or Travis, you can just define this as a build step (in fact, Travis will do it automatically). With the release of Xcode Bots, we haven't quite figured out a smooth process as of writing, but are working toward a solution, and we'll make sure to share it once we do.
CocoaPods streamlines development with Objective-C, and our goal is to improve the discoverability of, and engagement in, third-party open-source libraries. Understanding what's happening behind the scenes can only help you make better apps. We've walked through the entire process, from loading specs and sources and creating the
.xcodeproj and all its components, to writing everything to disk. So next time, run
pod install --verbose and watch the magic happen.