Choosing the Right iOS Dependency Manager
Over the years, the iOS platform has accumulated a large number of high quality third party libraries and frameworks. Leveraging existing frameworks can greatly speed up development time and reduce costs. Indeed, knowledge of third party frameworks is as relevant as knowledge of the platform itself. But with multiple dependencies to choose from, the question becomes how to best manage them in your code.
Java software engineers have long enjoyed several robust and feature-rich dependency management solutions like Maven and Gradle. In fact, these are more than just dependency managers – they are full-fledged build tools with hundreds of plugins that can do everything from running ProGuard on your compiled code to deploying it to AWS. However, these tools are not readily suitable to be used with iOS.
When it comes to writing iOS code, whether it’s Objective-C or Swift, you basically have three options: 1) integrate third party code into your project by hand without the use of a dependency manager, 2) use CocoaPods as your dependency manager, or 3) use Carthage as your dependency manager. A fourth option, Swift Package Manager, may become an option in the future, but for now it does not support the iOS platform. There are pros and cons to each of the three primary options, so let’s take a look at them all.
1) Integrating third party code by hand
In the early days it was necessary to integrate with the various third party bits by hand. This process typically starts with dropping a binary into your project and linking it. Sometimes frameworks also require additional steps, like adding custom linker and compiler flags. The nice thing about the manual approach is that you, the developer, are intimately familiar with how third-party libraries are integrated into your code. Additionally, you can be fairly certain that every existing iOS framework supports this method of integration. The downside, however, is that it’s not always trivial to upgrade a framework to a more recent version, and there is usually no way to quickly find out the current versions of all your dependencies. Over time, adding and removing third party frameworks manually can result in various dangling project settings. Your team will likely hesitate to touch them for fear of breaking something, unless you’ve documented all integration steps.
2) Using CocoaPods as a dependency manager
CocoaPods was the first official dependency manager for iOS, and at the time of this writing it is the most widely adopted. Virtually every third party library worth its salt supports it. CocoaPods is easy to set up. You simply create a Podfile and list your dependencies line by line and run pod install in the Terminal to embed them into your project. CocoaPods will create a new Xcode Workspace file that contains your project and all of the dependencies, neatly organized. This is both an advantage and a disadvantage. While the setup process is fairly simple, it does modify your project files in a non-transparent manner. Additionally, CocoaPods is centralized (like Maven and Gradle), so all dependencies are stored in a central repository. If that repository goes down for any reason, you may be sitting idle unable to compile your project. CocoaPods does allow you to specify additional repos (including those on your local file system), which works well for private frameworks stored in enterprise GitHub. CocoaPods does not impose a particular repository structure on you, so it provides the most flexibility of the dependency manager options in that regard.
3) Using Carthage as your dependency manager
Carthage is the newest dependency manager and it caters to those who desire the middle ground between the fully automatic integration of CocoaPods and the flexibility of the manual method. Carthage bills itself as the less intrusive of the two. Carthage is decentralized, and it currently supports several dependency sources: Github repositories (public, open source), Git repositories (everything else, such as enterprise), and binary links (public HTTPS). Setup is very similar to CocoaPods. Users create a Cartfile, add the dependencies, then run carthage update. The dependencies will be cloned and built locally using xcodebuild, or downloaded if they are of the binary variety.
There are some assumptions about your repository setup and build process that Carthage makes and CocoaPods does not. Your repository must be organized in a certain way for it to be usable with Carthage. If you previously had a framework with four submodules all living in the same Git repo and playing well with CocoaPods, you’ll probably need to break those up. If that is not an option, you may opt to serve these as pre-compiled binary frameworks with all your library’s dependencies baked-in. However, you can’t easily serve the binaries out of your enterprise Git repo, because that requires login credentials, especially if your organization uses SAML authentication. Carthage does not support this, so all binary dependencies must be accessible over HTTPS without authentication. An enterprise Nexus instance, which is the standard way of distributing internal Maven and Gradle dependencies, might be one such option, but you can’t just store your binaries in a tagged enterprise Git release and expect Carthage to be able to reach them. Enterprise support is still shaky at the moment, and a quick Google search brings up a host of issues.
However, in Carthage’s favor, if every framework had a standardized repository format, it probably would make things a bit cleaner. Carthage enforces simplicity by requiring you to structure your code in a predictable way. CocoaPods, on the other hand, will work with anything so long as you are willing to customize it. Carthage is still in beta, but is being actively developed and improved. It should certainly be on your organization’s radar. There is little doubt that it is poised to become the only functional alternative to CocoaPods.
So as you can see, if time is a constraint, you may want to hit the ground running by using CocoaPods simply due to the number of frameworks that support it. If time is not a constraint and your goal is standardized and clean code organization, then Carthage may be more attractive. It is important to be conscious of the fact that not all libraries currently support Carthage (yet), so you may end up having to integrate those by hand. While Carthage will download and build the correct dependency versions for you, you are still responsible for integrating the binaries into your project. This may include adding/removing project settings, compiler flags, and any custom scripts. But having a tool like Carthage to automate dependency download and neatly manage them in a single location on your file system, without modifying your project files, makes it difficult to argue in favor of the fully manual approach. The table below breaks down the various features of the three dependency management methods discussed:
|Supported by most active projects?||Yes||Yes||Not Yet|
|Dependency integration level of effort?||High||Low||Moderate|
|Level of effort for versioning and maintenance of dependencies?||High||Low||Low|
|Level of effort to make an existing framework compatible?||Low||Medium||Medium|
|Supports binary frameworks (“closed source”)?||Yes||Yes||Yes|
|Supports compiling from source?||Yes||Yes||Yes|
|Supports enterprise Git?||N/A||Yes||Sort Of|
|Supported by Fastlane?||Yes||Yes||Yes|
|In production (not “beta”)?||N/A||Yes||No|
One thing is abundantly clear: there is no reason not to use a dependency manager in your iOS project. Which one should you chose? That depends on how much control you wish to exercise over your project’s structure and whether you are willing jump on the early adoption bandwagon.
At the time of this writing, CocoaPods is on version 1.3.0, stable for production, supported by over 38,000 libraries and is used by over 2.7 million apps. Statistics for Carthage are much harder to come by, partly as a side effect of its decentralized nature. Carthage is still in beta, currently on version 0.26.2 and counting. Carthage is in very active development with over 100 contributors. Official Carthage releases come out frequently, often more than once per week. Both Carthage and CocoaPods have roughly an equal number of open GitHub issues, ~150 and ~100 respectively. Given how much simpler Carthage is compared to CocoaPods in terms of functionality, having more open issues than a dependency manager with 2.7 million users speaks both to the “work in progress” nature of the Carthage, as well as an active and enthusiastic community.
When developing an application, both CocoaPods and Carthage offer compelling benefits. But it is fair to say that CocoaPods holds the advantage in terms of speed and ease of use when setting up a new application project—for now. But that may change in the future.
When developing a framework (as opposed to an application) you should consider supporting both Carthage and CocoaPods. Carthage support is virtually free, provided you set up your source repository correctly from the start. Enabling CocoaPods support for your framework will not differ significantly regardless whether you also support Carthage or not. It should be noted that, for very simple frameworks with no dependencies of its own (like categories and extensions) it is much easier to make them Carthage compatible than to package them for CocoaPods. So definitely expect a large number of new frameworks to become Carthage compatible from the start.
Lastly, it is worth keeping an eye out for Swift Package Manager (SPM). It is, after all, the official package manager for the Swift programming language. There is no iOS support at the moment, but it is definitely coming. However, given the huge number of iOS applications still using Objective-C, it is unlikely that SPM poses any real near-term threat to either CocoaPods or Carthage. With that said, if you are writing a Swift iOS framework, it may be worthwhile to do the due diligence and future-proof your codebase such that your project can be compatible with SPM when/if iOS support is finally announced.