CocoaPods: working with internal pods without hassle

CocoaPods is cool. I was honoured to have a chat with Fabio Pelosin, the main contributor, at the NSLondon meetup some time ago and see how much passion those guys put in this project.

I was one of the first supporters back in 2012 and I have some Pods in the Specs repo (ADB prefixed). Recently, I spent several days going through some hidden aspects of CocoaPods, ending up reading some source code from the Core and Xcodeproj.

During one of the recent NSLondon(s) of 2013, Orta explained the advantages of CocoaPods and Abizer Nasir, in one of the following events, discussed the usage of Git submodules. Basically comparing their visions.

What I'm going to explain here is a solution to a common scenario:

Manage the versioning of internal private pods within projects without hassle.

Ok, let's start. You have your project under our DVCS (which I hope is Git for your sanity).

~/MyProject
    └── .git
    └── Sources
    └── MyProject.xcodeproj

You use some third-party components like AFNetworking and MagicalRecords and you have submodules for that, you hipster!

~/MyProject
    └── .git
    └── Sources
    └── MyProject.xcodeproj
    └── Vendor
        └── AFNetworking (submodule)
            └── .git
        └── MagicalRecord (submodule)
            └── .git

You decide to use CocoaPods, install it following the instructions and you add the Podfile.

platform :ios

pod 'AFNetworking', '~> 2.0'
pod 'MagicalRecord', '~> 2.2'

You remove the submodules and run pod install: the workspace is created for you and you're good to go. So far so good.

~/MyProject
    └── .git
    └── Podfile
    └── Sources
    └── MyProject.xcodeproj
    └── MyProject.xcworkspace
    └── Pods
        └── Pods.xcodeproj
        └── AFNetworking
        └── MagicalRecord

Then you realize that it'd be cool to have your own private repo of pods and create private pods for some parts of your project. You like modular things and maybe, one day your pods will be ready for a pull request to the Specs repo to contribute to the open source community.

So you create your own repo (mine is https://github.com/albertodebortoli/ADBCocoaPodsRepository) and add it to CocoaPods.

$ pod repo add REPO_NAME SOURCE_URL

Time to create a spec for your private pod, tag it and push it. Something like this:

Pod::Spec.new do |s|
  s.name     = 'MyInternalLibrary'
  s.version  = '1.0.0'
  s.platform = :ios, '7.0'
  s.summary  = 'My first pod!!!!111'
  s.homepage = 'https://github.com/me/MyInternalLibrary'
  s.author   = { 'John Doe' => 'john.doe@example.com' }
  s.source   = { :git => 'https://github.com/me/MyInternalLibrary.git', :tag => s.version.to_s }
  s.license      = { :type => 'New BSD License', :file => 'LICENSE' }
  s.source_files = '*.{h,m}'
  s.requires_arc = true
end

pod install and the situation is as follows:

~/MyProject
    └── .git
    └── Podfile
    └── Sources
    └── MyProject.xcodeproj
    └── MyProject.xcworkspace
    └── Pods
        └── Pods.xcodeproj
        └── AFNetworking
        └── MagicalRecord
        └── MyInternalLibrary
      
~/MyInternalLibrary
    └── .git
    └── MyInternalLibrary.podspec
    └── Sources
    └── MyInternalLibraryDemo.xcodeproj

You want to put the Pods folder in the .gitignore file.

Cool! But... there's a but. You're actively developing on MyInternalLibrary, you're touching those files several times a day. The files you're touching will be overwritten the next time you run pod install. Oh shit. You don't wanna open ~/MyInternalLibrary, touch things, pod install ~/MyProject over and over. It's not practicable.

Solution is to use development pods. When you install a development pod, its files are symbolically linked within Pods.xcodeproj.

The Podfile, now, specifies that the pod needs to be fetched from a local directory (with the use of the :path directive). A development pod does not require to be versioned with a Git repo, but it must contain the podspec which describes how to retrieve the pod files. This is your new Podfile:

platform :ios

pod 'AFNetworking', '~> 2.0'
pod 'MagicalRecord', '~> 2.2'
pod 'MyInternalLibrary', :path => '~/MyInternalLibrary'

That's great, now you can touch things safely and you can commit changes to MyInternalLibrary (in ~/MyInternalLibrary) while you are working on MyProject.xcworkspace.

Here comes the interesting bit.

You have a CI and deployment system that need to have a specific version of your internal pod. You are tempted to tag changes to MyInternalLibrary when deploying and use a specific Podfile in your release branch of MyProject.

platform :ios

pod 'AFNetworking', '~> 2.0'
pod 'MagicalRecord', '~> 2.2'
pod 'MyInternalLibrary', '~> 1.0.1'

Yeah... yeah... you're cool.

No, you're not.

Do you see the problem here? Keep the versioning/tagging of the internal pods updated is a pain in the ass. Developer's time should never be wasted this way.

I see you thinking...

"Damn, pods are not quite right here. I'm a hipster and a submodule would be perfect! But ufff... submodules or CocoaPods? Uff uff...".

The answer is... use both! Is the versioning/tagging of your library (just for accommodating CocoaPods) complex? Well, let the submodules handle it for you (using the sha1 of the commit, you know... Git things to point to the correct revision).

The ideal solution is to put your internal library inside the folder of the project and treat it as a git submodule:

~/MyProject
    └── .git
    └── Podfile
    └── Sources
    └── MyProject.xcodeproj
    └── MyProject.xcworkspace
    └── MyInternalLibrary (submodule)
        └── .git
        └── MyInternalLibrary.podspec
        └── Sources
        └── MyInternalLibraryDemo.xcodeproj
    └── Pods
        └── Pods.xcodeproj
        └── AFNetworking
        └── MagicalRecord
        └── MyInternalLibrary      

Your Podfile will be

platform :ios

pod 'AFNetworking', '~> 2.0'
pod 'MagicalRecord', '~> 2.2'
pod 'MyInternalLibrary', :path => './MyInternalLibrary'

When running pod install, files in ~/MyProject/MyInternalLibrary will be symbolically linked within Pods.xcodeproj.

Now you can

  • work on your project and change your libraries/pods while developing
  • commit changes to your libraries/pods independently of your project
  • tag/version your internal libraries/pods when you want to (not when it is requested by your project)
  • avoid the need of a specific Podfile for the release branch on MyProject

People way too often want to embrace CocoaPods fully, others prefer just submodules (and they are wrong, period). I think that the pragmatic solution to edge cases like this one is to use both!

Work as if it was just a submodule but with the unleashed power of CocoaPods!