Carthage vs. CocoaPods vs. Git submodules

Almost every project uses third-party libraries and/or frameworks. There are many approaches to using dependencies. Some advocate to implement every feature on their own, others choose to use every possible library not to invent the wheel again. Of course, there is no silver bullet and the truth lies somewhere in between. The more important aspect is a distinction between library and framework. I would define the library as codebase with particular use case e.g. progress HUD like SVProgressHUD or animation library like Pop.

On the other hand, framework typically consists of several aspects and gives structure for the application. Definitely, frameworks can be abstracted to enable change in the future. However, most of the time it’s very laborious and it’s not always profitable. E.g. there are currently two main frameworks for reactive programming in iOS — RxSwift & ReactiveCocoa. The decision to pick one or the other is quite important at the beginning, since both are designed for reactive programming, but have a bit different approaches. Changing framework in the middle of project would require a bit of work and is not as straight forward as changing progress HUD library. No matter what dependency we will use in our project, if it’s third-party library, local utils or company dependency, we need to manage them. There are 3 main approaches to handle dependencies during our job:

Git submodules

Depending on how one looks at that topic git submodules is not strictly a tool for managing dependency, but rather a technique to add libraries to project. The concept is quite simple — we have repos inside our repo. E.g. we create repo for our project and to add dependency we add submodule, which is another repo inside our project repo. This nesting can go on. If a library has dependencies and is using submodules, it’s also going to have repos nested inside.

Setting up git submodule is rather straightforward: git submodule add. The real problem with git submodules is maintaining dependencies updates and removing dependencies.

Since submodule is another repository, we have to manually update each dependency. Even thought there is an option to pull changes of submodules, there is no way to auto-update dependencies on the pull. That means every time library changes we need to run git submodule update --init --recursive.

Removing submodule is also not that easy as initialising. To look more deeply in the topic of git submodules I recommend reading Mastering Git submodules by Christophe Porteneuve. Of course, setting up git submodules is only part of the job. We need to configure our Xcode project to use dependencies, which includes adding library .xcodeproj to our project, setting deployment target, copying dynamic framework, etc.

Pros:

  • total control over dependencies
  • easy to include local dependencies

Cons:

  • a lot of manual steps
  • error prone
  • maintaining dependencies updates

I would agree with Christophe, that whenever you can use library management tool with automatic dependency and context resolving, you should do it.

Git submodules give total control over third-party libraries/local modules etc., however the price we have to pay to maintenance is quite high.

CocoaPods

CocoaPods is the most common dependency management tool in the iOS ecosystem. The project has started in 2011 and it’s mature and stable. There is a dedicated site (CocoaPods) for searching for dependencies. CocoaPods offers centralized ecosystem, which in theory provides easier library discovery. CocoaPods is build with Ruby and installation is as simple as running:

$ sudo gem install cocoapods

CocoaPods is command line tool. You configure it with simple text file named Podfile. For my last project Podfile looked like this:

platform :ios, '8.0'
use_frameworks!
target 'ConquerTheWorld' do
pod 'XCGLogger', '3.0'
pod 'SVProgressHUD', '1.1.3'
pod 'EZAudio', '1.1.1'
end

In order to install listed dependencies in the project, we need to run:

$ pod install

Now comes the very important part. CocoaPods is creating its own Xcode workspace file and it’s crucial to work on created one instead of original project file. E.g.

$ open ConquerTheWorld.xcworkspace

Everything looks straightforward. Installation is easy, learning curve is minimal, it’s almost standard in iOS community and there are tons of available libraries. Are there any drawbacks?

I would say it’s easy, rather than a simple tool. The difference is subtle, but you should see the presentation by Rich Hickey Simple Made Easy. CocoaPods is creating own workspace file and that is the place, where the magic happens. This tool is a bit like black box with monsters inside, you don’t want to dive in. This is the biggest issue.

Pros:

  • standard in iOS community
  • mature and stable
  • easy in use

Cons:

  • no direct control over project configuration
  • not a simple approach
  • blackbox in nature

Carthage

Last, but not the least there is Carthage. Carthage is more similar to Unix philosophy: do only one thing, but do it well. In contrast to CocoaPods, Carthage goal is only to manage dependencies, but not to integrate them with the project. Also, comparing to CocoaPods, Carthage is rather simple, than easy. You provide dependencies, Carthage builds them and provide binary frameworks. You obtain total control over project structure. There is no magic with setting up a new workspace for your project.

Carthage is build in a decentralized system in mind. There is no single place to look for libraries, but rather project discovery based on GitHub trends. That could be seen as both advantage and disadvantage. On the other side, in CocoaPods there’s a single website, which makes finding dependencies easier.

Carthage is also a command line tool. We can install it using Homebrew:

$ brew install carthage

Dependencies are defined in so called Cartfile, which again is a text file. E.x. if we will configure only one library - RxSwift

github "ReactiveX/RxSwift" ~> 2.0

In order to build download and build dependencies, we need to run:

$ carthage update

This command will download libraries to Carthage/Checkoutfolder and build them inside Carthage/Build folder. Since Carthage is only managing dependencies and does not integrate them into our project, we need to perform several steps more.

Firstly, on application target's General settings tag, in the Linked Frameworks and Libraries section, we need to drag&drop each framework we use from Carthage/Build folder.

Secondly, on application target’s Build Phases settings tab, click +, select New Run Script Phase and add following content:

/usr/local/bin/carthage copy-frameworks

Lastly, add paths to the frameworks you use under Input Files:

$(SRCROOT)/Carthage/Build/iOS/RxSwift.framework
$(SRCROOT)/Carthage/Build/iOS/RxCocoa.framework

Pros:

  • unix philosophy — do one thing
  • simple
  • total control over project structure

Cons:

  • more manual work
  • not as straightforward to use

Where to go?

We have three main possibilities for dependency management. All of them provide pros and cons for libraries control. I am a proponent of simple over easy approach. I feel simple tools provide more control and understanding over project structure.