Pods, also known as CocoaPods, are a necessary evil. And they are ubiquitous. Even if you’ve never used them, you probably know that they exist, and you may even have a general sense of what they are, because questions about them come up on Stack Overflow all the time. Basically, Pods are code libraries that you use in your project, along with a mechanism for rationalizing versioned dependencies amongst themselves.
When you’re at GitHub looking for a code library, you are very likely to see ReadMe instructions like this:
To install MyCoolLibrary with CocoaPods, add this line to your Podfile…
That means that if you want to use this library, you’re probably going to have to use it as a Pod.
Now, before we go any further, let’s get it out in the open: Pods are the work of the devil. They are a terrible mechanism for installing libraries. They do terrible things to your project. They are an absolute horror.
But for a long time, they have been the only real choice. If you want to incorporate someone else’s self-contained code library into your project in a clear, maintainable way, how are you going to do it? You might consider copying the library’s actual code files into your app target; but that might not be possible, or might be unacceptable for various technical or legal reasons, and in any case it isn’t going to help you with problems of versioning and dependencies.
Recently, Apple has jumped on the open source library bandwagon with Swift Packages, which became available in Xcode 11. This mechanism, too, handles versioning and dependencies; and Swift Packages are installed in a way that doesn’t pollute your project. New in Xcode 12, Swift Packages can include resources and localizations. So it looks like they are well on the way to replacing CocoaPods as the package manager of choice.
Nevertheless, let’s just accept for now that sooner or later, you have to install a Pod into your project. If you’re like me, the way this happened is that you’ve been called upon to help write some code in a pre-existing project, and that project already uses Pods. So you just have to use them, whether you like it or not. And now you’re sitting there with your mouth hanging open, with no clue about how to get started; and you don’t want the others on your team to know that you are completely clueless. If you’re like me, that is.
And so you go online and start googling to try to find a tutorial or a set of instructions that is clear, correct, up-to-date, telling you how to get started with Pods. And if you’re like me, you can’t find one.
Well, that’s why I’m here now. I’m going to tell you, as if you are a complete neophyte, how to get started with Pods.
CocoaPods are themselves installed and configured using yet another code library — a code library written in Ruby. In Ruby, a code library is called a gem. So the second thing you’re going to have do in order to get started with CocoaPods is to install that gem into your Ruby.
The reason that’s the second thing you’re going to have to do is this: before you can do anything in Ruby, you need to get control of your Ruby installation.
Here’s the problem. Mac OS X comes with a Ruby installation. And it sucks. Big time. First of all, it’s not up to date. Second, there’s no way bring it up to date, because it’s part of the system — it’s in /System/Library/Frameworks/Ruby.framework. Not only that, but anything you install into this Ruby is also part of the system: it goes into /Library/Ruby, and you can only perform such an installation by saying
sudo, which you can do only if you are a sudoer. This is an atrocious situation, and Apple knows it — and that’s why they have announced that, going forward, there won’t be any built-in Ruby installation. You’re going to be on your own. If you want Ruby at all, you’re going to have to install it yourself.
What you need to do, then, is to provide your own Ruby installation. And you need to provide it in such a way that it effectively overrides the built-in Ruby. That’s not easy to do if you try to do it on your own. Fortunately, you’re not on your own. There are some great mechanisms for installing your own Ruby, possibly more than one version of Ruby simultaneously. The main ones are
rbenv and RVM. The one that I use is
rbenv, and that’s the one I’m going to talk about now. If you’ve already got your Ruby under control, you can skip the rest of this section.
Install Xcode Tools
First, before you do anything else, make sure your developer command-line tools are correctly configured. If necessary, go to https://developer.apple.com/download/more/ and download the Command Line Tools corresponding to your Xcode version and run the installer. Also, in Xcode, open the Locations pane of the Preferences window, and use the Command Line Tools pop-up menu to point at this copy of Xcode. (You’ll have to give your admin username and password when you do that.)
To check that things are correctly set up, in the Terminal, say this:
% xcode-select -p
The result should be something like this:
In other words, we should be pointing into the Developer folder inside the Xcode that you are actually using.
We now want to install
rbenv. When you go to https://github.com/rbenv/rbenv for instructions, you find that they suggest using
brew. That means Homebrew. Have you installed Homebrew? Is it up to date? In the Terminal, type this:
% which brew
You should see this:
If you do, then type
brew update to make sure you’re up to date. If you see nothing, you need to install Homebrew from scratch. Go to https://brew.sh and do exactly what it says: they give you a line of bash code that you copy and paste directly at the Terminal prompt (and hit Return).
% /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
You’ll be asked for your password, and
brew will be installed.
WARNING: My experience is that installation of
brewis simple in a new clean admin user account, even in Catalina. However, older accounts may encounter permissions issues. Dealing with these is beyond the scope of this discussion.
It turns out that
rbenv is present as part of your
brew installation. But you still don’t have an actual copy of Ruby! To confirm that fact, say this:
% rbenv versions
If all you see is
system, you need to install a version of Ruby. To find out what versions there are, say:
% rbenv install -l
You’ll see a huge list, but the one you want is the last one with no prefix and no suffix. For example, at this moment, in the middle of the list, I see this:
The last version with no prefix and no suffix is
2.6.3. That’s the current version of Ruby! So that’s the one we want. So say:
% rbenv install 2.6.3
Downloading and installing Ruby takes quite a long time, so go do something else for a while.
The result is two-fold. First, you do now have Ruby 2.6.3 — though you are not yet using it. Second, a folder called .rbenv has been created in your home directory. (This folder is normally invisible because its name starts with a dot.) This is where your new Ruby is going to live.
Now that you have Ruby 2.6.3, you need to start using it instead of the built-in Ruby. To do so, say:
% rbenv global 2.6.3
You should also tell
rbenv to clean itself up:
% rbenv rehash
That’s good, but you still need to ensure that this version of Ruby, managed by
rbenv, stands in front of the system Ruby. To find out how to do that, say
% rbenv init
This doesn’t actually do anything; instead, it tells you what to do. In particular, you’re going to need to modify your shell’s initialization script file; you’ll be told what that file is called and how you need to modify it. For example, if you’re using
bash as your shell, you’ll be told:
# Load rbenv automatically by appending
# the following to ~/.bash_profile:
eval "$(rbenv init -)"
And if you’re using
zsh, you’ll see this:
# Load rbenv automatically by appending
# the following to ~/.zshrc:
eval "$(rbenv init -)"
So do that! The easy way, which works regardless of whether you actually have an initialization script file, is to use
echo and the append operator
>>. So, for example:
% echo 'eval "$(rbenv init -)"' >> .zshrc
When that’s done, quit the Terminal. The point is that the next time you launch the Terminal, your shell will execute the line you’ve just created in its initialization script, and you’ll be using the Ruby you installed into
rbenv. To prove that you’re using it, just ask about Ruby:
% which ruby
% ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin19]
Getting the gem
Now that you’re in control of your own Ruby installation, you can install the Ruby gem that implements CocoaPods:
% gem install cocoapods
rbenv to clean up:
% rbenv rehash
Not only have you now installed a Ruby gem; you’ve also installed an associated command-line tool, namely
pod. To see this, ask where it is:
$ which pod
You should see something like this:
That proves that you have the
pod tool and that it’s in the right place, namely, inside your
Some tutorials at this point suggest that you also say
pod setup, but I don’t see any particular need for that.
Add a Pod
At long last we’re ready to add our first Pod to an actual Xcode project! Start by making a project. This can be a plain vanilla Xcode project drawn from any of the templates, such as the Single View App template.
Let’s say we want to incorporate the SnapKit Pod into this project. I’ve picked SnapKit pretty much at random; it can be anything. (To find out more about SnapKit, go to https://github.com/SnapKit/SnapKit.)
So let’s go for it! Here’s what to do.
First, quit Xcode. That’s right, quit Xcode. Pods work by performing horrible surgery on your project. Your project cannot be open during that surgery, and the best way to ensure that is to quit Xcode altogether.
Second, whip out any text editor. I like BBEdit but you can use anything you like. Make a new text file and save it into your project folder, using the filename podfile — just that, podfile and no more. Look at the file in the Finder! Make sure you have not accidentally added a .txt suffix.
Still editing your podfile text file, give it this content (I’m assuming you’re targeting iOS 13, which is more or less current as of this writing):
platform :ios, '13.0'
target 'MyProject' do
That’s Ruby code! The reason is that the CocoaPods mechanism, which runs in Ruby, is going to find this file and load and execute it.
Once you’ve made the podfile contain that code, you can quit your text editor.
And now, the moment of truth. Open the Terminal and
cd into your project folder. (As you probably know, the easiest way to do that is to type
cd and a space at the command line, then drag the project folder from the Finder right onto the Terminal window, and then, in the Terminal again, hit Return.) And now say the magic words:
% pod install
The result is that your original project has been modified in horrifying ways. As the message in the Terminal informs you, this project is no longer your own. Let’s say your project is called MyProject.xcodeproj. Well, you can forget about that file from now on. You must open your project using a different file, MyProject.xcworkspace, which has been created for you. I strongly recommend that before doing that, you delete the contents of DerivedData.
Okay, are you ready to open MyProject.xcworkspace? Locate it in the Finder and double-click it to launch Xcode and open the project window. As you can see, your workspace now contains two projects, your own MyProject and a newly added Pods project. Moreover, your MyProject has been sullied with a Pods group and a Frameworks group. There’s nothing you can do about that all; it’s just how Pods work.
The outcome is that your code (in MyProject) can
import SnapKit and use SnapKit classes and methods. That was the goal. You’ve installed a library as a Pod.
Sometimes you need to uninstall the Pods from your project and start over. The way to do that is, having quit Xcode, to
cd into your project folder and say:
% pod deintegrate
This removes the Pods from your project. But it leaves the podfile in place, so if you now say
pod install again, you have effectively started over. Sometimes, that sort of cleaning is just what you need.