Handling iOS dependencies with CocoaPods Rome and git-lfs

01 Aug 2020

How many 3rd party dependencies do you have?

Handling the 3rd party dependencies for an iOS project is not the simplest thing to do.

I’m saying this because:

  • ideally, projects should use just a limited number of 3rd party dependencies, for very good reasons and with a very high quality. For me, a good quality library / 3rd party is a well maintained, widely used, nicely documented and (almost) fully covered with unit tests. So we can rely on that code and any future changes to it.

  • as the guys from Essential Developer say:

    You should trust the 3rd party works as intended. If you don’t trust it, you should not use it!

  • in reality, many projects got used to adding more and more 3rd party dependencies (for many reasons)

  • handling many dependencies manually (by adding their files to your repo, either by copy-pasting or git submodules) is hard and takes a long time

  • CocoaPods, Carthage and now Swift Package Manager are good tools for managing those dependencies, but we sometimes need a combination of their features

  • some 3rd parties just don’t support one or more of those dependency managers

  • Xcode doesn’t deal well with a lot of source files (anyone familiar with the never-ending Indexing…?)

    • for many projects, the size of the 3rd party source files is way bigger than the size of its own source files
    • therefore the CocoaPods way is non-optimal (Pods.xcodeproj is added to your workspace and the pod targets are built every time)

Dependency management on other platforms

Our community has always learned from other communities.

Java

If we look at the way Java projects handle their dependencies via maven, we’ll see they have nice features like:

  • text file to specify which dependencies and what versions they need (pom.xml)
  • maven can just install the compiled libs, saving the time to build on each machine
  • maven has a cache layer

Ruby

Handling ruby dependencies is similar to Java’s, by using bundler.

  • Gemfile to define the dependencies and any version constraints, Gemfile.lock is generated by bundler to keep track of the exact gems and their versions. Very similar to the Podfile / Podfile.lock system CocoaPods uses (no surprise since CocoaPods is a Ruby gem)
  • the gems are Ruby libraries

My requirements of the dependency management system

First of all, I would only do this if I have a lot of dependencies that take a significant amount of time to build / link / index. That’s a call you have to make based on your project’s setup.

Nevertheless, I want my dependency management system to:

  • be supported by all the 3rd parties I need (not that easy to achieve :) )
  • for those that don’t have support for it, to be super easy to add it
  • use a simple text file to keep track of all the dependencies (so we can check it in and track its changes)
  • avoid any additional projects and overloading Xcode with many source files
  • cache precompiled versions of each dependency
  • that cache should be available to all dev machines and to any CI machines as well, to speed up the builds

My (complex) solution

To satisfy all those requirements, when the project’s number of dependencies is big, I use the following combination

  • CocoaPods as the dependency management tool
  • CocoaPods’ Rome plugin that allows just building the dependencies instead of adding them to the workspace
  • use_frameworks! :linkage => :static inside the Podfile so all the dependencies are built as static frameworks
  • git-lfs (which is a git plugin that was built to deal with large / binary files) to store those precompiled static frameworks (binaries) inside the git repo and, thus, make for that caching layer used by all dev and CI machines

The disadvantages

  • it’s a pretty complex setup
  • it requires some time to set up, since all the precompiled frameworks need to be manually linked to your project (and some of them require extra configurations)
  • when updating one dependency, your pod install command will rebuild everything (there’s no way to specify building just one dependency)
  • if you need to use dependencies that don’t support CocoaPods, you’ll have to write a podspec for each of them and keep it in a private Specs repo
  • the Rome project is almost abandoned. It still works, but in the future it may not. Having an abandoned tool at the root of your dependency management system is not a good idea.

The advantages

  • your project will build a lot faster, as all those dependencies are prebuilt (once)
  • Xcode will index and manage the source files easier and quicker
  • if you use SwiftUI previews, they will also build faster (as those trigger real builds before running)
  • you have a central place (the Podfile) where all your project’s dependencies are expressed
  • you can customize certain steps (CocoaPods is the most customizable of the 3). For example, you may need to set a SWIFT_VERSION for some dependencies that didn’t set one on their own
  • you are still able to manage / update dependencies through CocoaPods (using pod install or pod update), which is a lot simpler to do than update each dependency manually
  • by using static frameworks, your app will link those statically, which means it will include just the architectures it requires and your startup time will be faster than using dynamic frameworks (this is another complex subject)

Tags: Dependency management Cocoapods Carthage Swift Package Manager Rome git git-lfs


Loading...