iOS Development: Pushing CocoaPods to the Limit
There is a magical tool that I have loved to use ever since I started my iOS development journey, and that is CocoaPods – an extremely easy-to-use tool that allows iOS developers to pull in the libraries and dependencies they need so that they can quickly focus on their own projects. Needless to say, I love CocoaPods; I definitely saw the potential for the speed and agility a tool like this could offer any iOS development team.
For the last year or so, I have been trying to leverage CocoaPods in the greater context of Continuous Integration systems. Below are tips and tricks that I want to show other iOS developers out there that are not readily evident in the CocoaPods documentation, but that have been extremely beneficial to the speed and elegance that we all strive for in our work. Below are some ways that I have found that really push CocoaPods to its limits.
Take Advantage of Multiple Targets
An extremely powerful feature of CocoaPods is to give the user the ability to define exclusive Pod dependencies specific to a target. In my development, I sometimes find myself needing to add libraries that should really only exist in my development builds or in my automation tests or unit tests – dependencies that have no need to be linked in my build phases. To do this with CocoaPods, I would do the following:
# Specific Dependencies for my Development builds target 'MyDevTarget', :exclusive => true do pod 'Reveal-iOS-SDK', '~> 1.0.4' end
As can be seen above, the Reveal iOS SDK will be downloaded, configured, and linked exclusively with MyDevTarget. Note that the impetus behind me wanting to link the Reveal iOS SDK (a powerful view inspection tool) only with this target is that it should not be bundled with an App Store build, but is still a useful library to include when debugging in-house builds.
Another instance illustrating the usefulness of this feature is with my test targets:
# Specific Dependencies for my Unit Tests target ‘MyUnitTestTarget', :exclusive => true do pod 'OCMock', '~> 2.2.4' pod 'Kiwi', '~> 2.2.4' end # Specific Dependencies for my UI Tests target 'MyUITestTarget', :exclusive => true do pod 'KIF', '~> 3.0.3' end
Now my Podfile is equipped with the ability to add specific libraries pertinent to these test targets. As illustrated, OCMock, a great implementation of mock objects, is added only to my unit test target as it is not needed in my development, beta, or production builds. Similarly, KiF, a great UI acceptance testing project, really only needs to be added when I run my UI tests.
Taking advantage of the above syntax will give great expressive power to our Podfile, and will really push CocoaPods to the limit to work with our Continuous Integration system. With it, we can ensure that extraneous dependencies do not seep into our different builds. If you have not done so yet, it is recommended these development targets be created in your Xcode project to start taking full advantage of this feature in CocoaPods.
Define Common Pods Across Multiple Targets
Let’s continue with the situation above where we now have multiple targets. such as MyDevTarget and MyProdTarget. Given that we have these separate targets, how do we define common dependencies that should be included in every build? Simple, by taking advantage of the fact that Podfiles are written in Ruby underneath, like this:
# Define common pods in this function def myCommonPods pod ‘AFNetworking’, , '~> 2.3.1’ pod ‘CocoaLumberjack’, , '~> 1.9.0' pod ‘CustomUIActionSheet’, '~> 0.1.1' end target 'MyDevTarget', :exclusive => true do # Define specific pods to the target here pod 'Reveal-iOS-SDK', '~> 1.0.4’ # Define common pods here by calling our new method above myCommonPods end target 'MyProdTarget', :exclusive => true do # Define common pods here by calling our new method above myCommonPods end
The example above shows how simple it is to express shared libraries we want to link across targets in an extremely eloquent way. Here we take advantage of a little Ruby and define a function that lists out the commonalities for us. The advantage of this method is in its simplicity: we do not have to repeat ourselves in our Podfile – underneath the hood, we leverage the fact that CocoaPods will generate the config files, prefix headers, and linkers for us automatically.
Miscellaneous Tips: warnings, post install hooks, branches, and more
The following are just a few miscellaneous tricks I have found useful during development:
Use inhibit_all_warnings! to suppress all warnings that may come from the pods that you pull into your project
Use the post_install hook to run code that you need to be executed after all the pods have been installed:
post_install do | installer | … end
There is no need to support multiple podfiles across your branches (development, integration, etc…). You can use the magic of Ruby to detect what branch your project is currently on and tell CocoaPods to pull the appropriate pod dependency, all in one Podfile! Note that you will have to run a pod install command every time you switch branches for this to work.
Always put your Podfile.lock file under source control. Doing so ensures that the dependencies you pull into your development environment will be consistent with other team members or other machines.
With the above tips, you should be able to really push CocoaPods to the limit without much effort. Feel free to leave a comment with any questions or comments you may have, and I will try my best to answer them for you!