I have been meaning to look at CocoaPods as a way of managing dependencies on third party libraries for some time. There is a good Introduction to CocoaPods over on NSHipster. You should read that together with Mattt Thompson’s article on Stewardship on Open Source communities. I will quote just one sentence:
It’s not enough to dump a pile of source code somewhere and declare it “open source”.
So partly because I want to be a better community member but also to gain some practical experience in creating a CocoaPod I decided to revisit my old iOS Keychain wrapper project. This post collects my notes on converting and releasing that project as a CocoaPod which I hope will be useful to others. If you are a CocoaPod guru and I missed a trick let me know.
Installation
CocoaPods is a Ruby gem which can be a cause for alarm if you are not familiar with Ruby. Luckily OS X ships with a version of Ruby pre-installed which will run CocoaPods fine. Installation is as simple as typing the command:
$ sudo gem install cocoapods
Note: I am using the Ruby Version Manager rvm to manage Ruby and gem dependencies which removes the need for the sudo:
$ gem install cocoapods
For further installation details check the CocoaPods Getting Started Guide.
Preparation
Before getting into creating a CocoaPod I will clean up the project a little and adopt some good practises.
Project Organisation
Life will be easier if the library files are all under a single directory structure. The latest versions of Xcode separate the Xcode files, project files and test files into separate directories. I have reorganised the project files into three main directories to create a similar setup:
- PasswordManager - contains the class files for the library
- PasswordManagerTests - contains the unit test files
- ExampleApp - contains the example app code for the library
Tagging a Release
CocoaPods rely on semantic versioning which in simple terms means using a version number like this:
MAJOR.MINOR.PATCH
Increment MAJOR version for incompatible API changes.
Increment MINOR version for backwards-compatible changes.
Increment PATCH version for backwards-compatible bug fixes.
It is important that the version in the Podspec file (which I will explain shortly) also exists as a tag in the git repository. I have not been good at tagging the repository so will fix that by adding an initial tag for version 0.0.1 and pushing that to the repository:
$ git tag -a 0.0.1
$ git push —tags
Once I have everything working I will update the tag and the Podspec file to 1.0.0.
Adding a License
Before you can add a library to the CocoaPod repository it must have a valid license file. There are a lot of useful sites if you want help on choosing an open source library. My project already contains a BSD-style license file in the project root directory so I am good to go.
Documenting a Library
It seems obvious but if you want other people to use your library it needs to be well documented. This means you should have a README file in the project root and for an Objective-C library create appledoc style documentation.
My library already has a pretty good README which helped when adding the missing appledoc comments to the library header file. Once again NSHipster has a good guide to get you started on appledoc.
Tests
The final preparation step is to have a good test suite for the library. CocoaPods does not dictate which test framework you use. My library already has a set of OCUnit based tests that still work for iOS 6 so I will stick with them.
Creating the CocoaPod
Creating the Podspec File
The Podspec file describes the version of a library and includes basic metadata such as the name, version, description and the location of the source repository. The Podspec file lives in the root project directory. You can create it from scratch but the easiest way is to use the pod spec create
command with the path to the repository:
$ pod spec create https://github.com/kharrison/UYLPasswordManager
Specification created at UYLPasswordManager.podspec
If you have not yet tagged the repository you will get a warning and the Podspec will use version 0.0.1. Either way you will get a well commented initial version of a spec pre-populated with some basic data from the repository. Don’t forget to remove the comments. The final version is below:
Pod::Spec.new do |s|
s.name = "UYLPasswordManager"
s.version = "1.0.0"
s.summary = "Simple iOS Keychain Access"
s.description = <<-DESC
The UYLPasswordManager class provides a simple wrapper around Apple Keychain
Services on iOS devices. The class is designed to make it quick and easy to
create, read, update and delete keychain items. Keychain groups are also
supported as is the ability to set the data migration and protection attributes
of keychain items.
DESC
s.homepage = "https://github.com/kharrison/UYLPasswordManager"
s.license = { :type => "BSD", :file => "LICENSE" }
s.authors = { "Keith Harrison" => "keith@useyourloaf.com" }
s.social_media_url = 'https://twitter.com/kharrison'
s.platform = :ios, "6.0"
s.source = { :git => "https://github.com/kharrison/UYLPasswordManager.git", :tag => "1.0.0" }
s.source_files = "PasswordManager"
s.requires_arc = true
end
Spec Metadata
The initial metadata section of the Podspec file is straightforward and mostly populated from the repository. I only needed to add the description text and change the homepage URL to point to the github project page.
s.name = "UYLPasswordManager"
s.version = "1.0.0"
s.summary = "Simple iOS Keychain Access"
s.description = <<-DESC
The UYLPasswordManager class provides a simple wrapper around Apple Keychain
Services on iOS devices. The class is designed to make it quick and easy to
create, read, update and delete keychain items. Keychain groups are also
supported as is the ability to set the data migration and protection attributes
of keychain items.
DESC
s.homepage = "https://github.com/kharrison/UYLPasswordManager"
Spec License
I am using a BSD license stored in a file named LICENSE in the project root so we can specify that in the Podspec file:
s.license = { :type => "BSD", :file => "LICENSE" }
Author Metadata
The author or maintainer of the library. If you prefer you can omit the email address.
s.authors = { "Keith Harrison" => "keith@useyourloaf.com" }
You can also add a social media URL. If you specify a Twitter account it will receive notifications from the @CocoaPodsFeed account.
s.social_media_url = 'https://twitter.com/kharrison'
Platform Specifics
The supported platform and deployment target. This is an iOS only library that supports iOS 6.0 and later:
s.platform = :ios, "6.0"
Source Location
The location of the source repository. Typically the Git repository and release tag:
s.source = { :git => "https://github.com/kharrison/UYLPasswordManager.git", :tag => “1.0.0” }
Source Code
With the library files cleanly contained in a single directory the source file location is easy:
s.source_files = "PasswordManager"
Build Settings
Not yet a default so we need to say if we expect ARC:
s.requires_arc = true
Checking the Podspec File
Before committing the Podspec file to the repository we can test from the project directory.
$ pod lib lint
-> UYLPasswordManager (1.0.0)
UYLPasswordManager passed validation.
Assuming everything passes we can make the final commit of the changes to the repository, create the tag and push it to the repository. I first pushed a beta version (1.0.0-beta) to allow me to check the AppleDocs. When I was sure the CocoaPod was good I updated to 1.0.0:
$ git tag ‘1.0.0’
$ git push —tags
To test against the files in the repository:
$ pod spec lint UYLPasswordManager.podspec
-> UYLPasswordManager (1.0.0)
Analyzed 1 podspec.
UYLPasswordManager.podspec passed validation.
Testing
Before releasing the CocoaPod to the world it is a good idea to test it in a real project.
Creating a Test Project
Create a new Xcode project to exercise the library and add the Podfile to the project root directory
platform :ios, '7.0'
pod 'UYLPasswordManager', :git => 'https://github.com/kharrison/UYLPasswordManager.git'
The git reference will retrieve the CocoaPod directly from the Git repository without referring to the central CocoaPods directory. To install the pod, from the project directory:
$ pod install
This will create an Xcode workspace which you can use to check the library installed without problems. Take a look at the Using CocoaPods guide for more details on adding pods to an Xcode project.
Publishing the CocoaPod
The CocoaPods Trunk Service is now the preferred way to publish a CocoaPod.
Register
Before you can publish a CocoaPod you need to register with the CocoaPods Trunk service. Make sure you use the email address that you use for committing and verify by clicking on the link in the email you receive.
$ pod trunk register keith@useyourloaf.com “Keith Harrison”
Publish to Trunk
The final step is to push the Podspec to the trunk service:
$ pod trunk push UYLPasswordManager.podspec
If you included a Twitter account in the Podspec you should see a notification within a few minutes announcing the new pod to the world.