Edmond and Edmond share the details, principles, source code, practices and experiences of CocoaPods, a version management tool for iOS/macOS projects, to help you understand the dependency management tool. Not just pod Install and POD Update.
Contents of this article
background
CocoaPods is an industry standard that iOS developers are familiar with. However, most students use CocoaPods through pod Install and POD Update. Once the project is componentized and the logic of each line of business is split into an independent Pod, it is not enough to understand a few simple Pod commands. At the same time, the consistency of the development environment and various abnormal errors in the execution of Pod commands require us to have a deeper understanding and 🤔.
There are many in-depth articles about CocoaPods. I recommend ObjC China for an in-depth understanding of CocoaPods[1]. In this article, I want to talk about CocoaPods management philosophy from the perspective of dependency management tools.
Version Control System (VCS)
Version control systems are a category of software tools that help a software team manage changes to source code over time. Version control software keeps track of every modification to the code in a special kind of database.
In software engineering, version control system is an important part of agile development, which provides a guarantee for continuous integration. Source Code Manager (SCM) Source Code management is within the scope of VCS, with well-known tools such as Git. The Package Manger (PM) provided by CocoaPods for various languages can also be considered a type of SCM.
For example, Git or SVN is for the versioning of a single file of a project, while PM is for each independent Package as the smallest unit of management. Package management tools are used in conjunction with the SCM to do the management work. For files that rely on libraries taken over by the PM, Git’s.ignore file is usually ignored.
For example, node_modules files are ignored in Node projects and Pods are ignored in iOS/macOS projects.
Git Submodule
Git submodules allow you to keep a git repository as a subdirectory of another git repository. Git submodules are simply a reference to another repository at a particular snapshot in time. Git submodules enable a Git repository to incorporate and track version history of external code.
Git Submodules are a youthful version of PM, embedding individual Git repositories as subdirectories in your working directory. It does not have the unique semantic version [2] management of PM tools, and cannot handle dependency sharing and conflict. Only the file state of each dependent repository can be saved.
When Git commits an update, it takes a snapshot of all the files and stores it in the database. Git manages files in three states:
- Working director: Files that are visible to the naked eye
- Stage area:Temporary storage area (or
index area
),.git/index
Directory, save the executiongit add
Files added from the working directory after related commands. - Commit the history:Submit history, exist
.git/
Under the directory, the file changes to this state is considered as a successful repository, the basic will not be lost.
Git submodules rely on.gitModules files to record submodules.
[submodule "ReactNative"]
path = ReactNative
url = https://github.com/facebook/ReactNative.git
Copy the code
.gitModules only records basic information about the path and URL and module name, but we also need to record the commit information for each Submodule Repo, which is recorded in the.git/modules directory. Git also ignores paths added to.gitmodules.
Package Manger
As an enhanced version of Git Submodule, PM is basically equipped with semantic version checking ability, dependency recursive search, dependency conflict resolution, as well as the ability to build specific dependencies and binary packages. A simple comparison is as follows:
Key File | Git submodule | CocoaPods | SPM | npm |
---|---|---|---|---|
Description file | .gitmodules | Podfile | Package.swift | Package.json |
Latches file | .git/modules | Podfile.lock | Package.resolved | package-lock.json |
As you can see from 👆, the PM tool basically implements package management around these two files:
- Description files: declare what dependencies and version limits exist in the project;
- Lock file: Records the full version list of dependent packages when they were last updated.
In addition to these two files, centralized PMS typically provide package-dependent hosting services, such as NPMjs.com [3], provided by NPM, which centrally finds and downloads NPM packages. Decentralized PMS such as iOS Carthage and SPM can only use Git repository addresses.
CocoaPods
CocoaPods is a dependency management tool for developing third-party libraries for iOS/macOS applications. With CocoaPods, you can define your own dependencies (Pods for short) and versioning third-party libraries throughout your development environment is easy.
Let’s take CocoaPods as an example.
Podfile
Podfile is a file that describes dependencies in a DSL (directly in Ruby syntax) that defines the third-party libraries that a project needs to use. The file is highly customizable and you can customize it to your personal preference. See the Podfile guide [4] for more information.
Podfile.lock
This is one of the most important files CocoaPods creates. It records each installed version of the Pod that needs to be installed. If you want to know which version of Pod you have installed, check out this file. It is recommended to add the podfile. lock file to version control to help with consistency across the team.
Manifest.lock
This is a copy of the podfile.lock file that is created each time the pod Install command is run. If you’ve ever encountered The error that The sandbox is not in sync with The podfile. lock file, This is caused by a discrepancy between the manifest. lock file and the podfile. lock file. Since the directory where Pods are located is not always under version control, this ensures that developers can update their Pods before launching the App, otherwise the App may crash or fail to compile in some less obvious place.
Master Specs Repo
Ultimately, the goal is to improve discoverability of, and engagement in, third party open-source libraries, by creating a more centralized ecosystem.
As a package management tool, CocoaPods aims to provide us with a more centralized ecosystem to improve discoverability and engagement with dependent libraries. This is essentially to provide better retrieval and query functionality, but unfortunately becomes one of its problems. Because CocoaPods manages these registered dependency libraries through the official Spec repository. As more and more dependent libraries are added, Spec updates and maintenance become a burden for users.
Fortunately, this issue has been resolved in 1.7.2. CocoaPods provides Mater Repo CDN[5], which can be CDN directly to the Pod address without having to go through the local Spec repository. In version 1.8 at the same time, the CDN is the official Spec warehouse has been replaced by default, the address is https://cdn.cocoapods.org [6].
Ruby ecology and toolchain
For some students who have only been exposed to CocoaPods, their PMS may not be familiar with them. CocoaPods borrows its ideas from PM tools in other languages, such as’ RubyGems’ [7], ‘Bundler’ [8], ‘NPM’ [9] and ‘Gradle’ [10].
We know that CocoaPods is implemented in the Ruby language. It is a Gem package in itself. Understanding Ruby’s dependency management helps you better manage different versions of CocoaPods and other gems. Being able to make sure that everyone on the team is using the same version of the tool is an agile development guarantee.
RVM
& rbenv
`RVM`[11] 和 `rbenv`[12]Both are tools for managing multiple Ruby environments, and both provide for managing and switching between different versions of Ruby environments.
Which is better depends on personal habits. Of course, rbenv officially says Why Rbenv [13]. The subsequent experiments in this paper are also demonstrated by using RBENV.
RubyGems
The RubyGems software allows you to easily download, install, and use ruby software packages on your system. The software package is called a “gem” which contains a packaged Ruby application or library.
RubyGems[14]Ruby is a package management tool for Ruby, which manages tools written in Ruby or dependencies we call gems.
RubyGems also provides a hosting service for Ruby components that centrally locate and install libraries and apps. When we use gem install XXX, we will query the corresponding gem Package through rubygems.org. Many of the tools used in everyday iOS come from Gems, such as Bundler, Fastlane, Jazzy, CocoaPods, etc.
By default Gems always downloads the latest version of library, which does not ensure that the library version installed is what we expect. So we’re missing one more tool.
Bundler
Bundler[15]Gem is a tool for managing Gem dependencies, isolating Gem versions and dependencies in different projects, and is also a Gem.
Bundler provides a stable application environment by reading Gemfile, a dependency description file in the project, to determine the version number or range of each Gems. When we use Bundle install it generates gemfile. lock and writes the version number currently used by Librarys to it. When libaray is installed via bundle install, librarys, version information, and so on are read from gemfile. lock.
Gemfile
To say theCocoaPods
RubyGems + Bundler for iOS. Bundler is based on the projectGemfile
Files to manage Gem, whileCocoaPods
Manage pods through podfiles.
Gemfile[16] is configured as follows:
source 'https://gems.example.com' do
gem 'cocoapods'.'1.8.4'Is a tool for managing Gem dependencies gem 'another_gem'.:git= >'https://looseyi.github.io.git'.:branch= >'master'
end
Copy the code
As you can see, Podfile’s DSL is written in the same way as Gemfile. So when would Gemfile be used?
Since CocoaPods releases major updates every year, as we discussed earlier, CocoaPods makes changes to the.xcodeProj file for the project during install, which may cause a number of conflicts when changes are made. The project won’t work. I don’t think you want to fix the.xcodeProj conflict.
If the project is based on Fastlane for continuous integration and App packaging, it will also need its version management capabilities.
How do I install a manageable Ruby toolchain?
Now that we’re done with the division of these tools, let’s move on to the actual use of these tools. We can use homebrew + Rbenv + RubyGems + Bundler to control the version dependency of a Ruby tool in a project.
Here’s what I think is a manageable hierarchy of Ruby toolchains. Let’s talk about how each layer is managed and how it actually works.
1. Usehomebrew
The installationrbenv
$ brew install rbenv
Copy the code
After the installation is successful, enter rbenv to see the relevant prompt:
$ rbenv
Rbenv 1.1.2Usage: rbenv <command> [<args>]
Some useful rbenv commands are: commands List all available rbenv commands local Set or show the local application-specific Ruby version global Set or show the global Ruby version shell Set or show the shell-specific Ruby version install Install a Ruby version using ruby-build uninstall Uninstall a specific Ruby version rehash Rehash rbenv shims (run this after installing executables) version Show the current Ruby version and its origin versions List installed Ruby versions which Display the full path to an executable whence List all Ruby versions that contain the given executable See `rbenv help <command>' for information on a specific command. For full documentation, see: https://github.com/rbenv/rbenv#readme Copy the code
2. Userbenv
Managing Ruby Versions
To install a Ruby version using rbenv, I used just Release Ruby 2.7:
$Rbenv install 2.7.0
Copy the code
The installation process is a bit long, as it takes about 20 minutes to download the OpenSSL and Ruby interpreter.
After the installation is successful, we make it work in the local environment:
$Rbenv shell 2.7.0
Copy the code
An error may occur after you enter the preceding command. Rbenv prompts us to initialize the rbenv environment by adding an eval “$(rbenv init -)” statement to.zshrc. If an error is reported, we can add and restart the terminal.
$ ruby --version
X86_64-darwin19 ruby 2.7.0P0 (2019-12-25 Revision 647F091)$ which ruby
/Users/gua/.rbenv/shims/ruby
Copy the code
After the switch, we find that Ruby has switched to the managed version of Rbenv and its startup PATH has changed to Ruby managed by Rbenv. And we can look at the PATH of the Gem that Comes with Ruby:
$ which gem
/Users/bytedance/.rbenv/shims/gem
Copy the code
The corresponding Gem has also become a PATH in Rbenv.
3. Query the system levelGem
Rely on
Thus, we have versioned Ruby and its Gem environment with Rbenv. We can use the gem list command to query all gem dependencies in the current system:
$ gem list
*** LOCAL GEMS ***
Activesupport (4.2.11.3).Claide (1.0.3)Cocoapods (1.9.3)Cocoapods - core (1.9.3)Cocoapods - deintegrate (1.0.4)Cocoapods - downloader (1.3.0)Cocoapods - plugins (1.0.0)Cocoapods - search (1.0.0)Cocoapods - stats (1.1.0)Cocoapods - trunk (1.5.0)Cocoapods - try (1.2.0)Copy the code
Keep in mind the CocoaPods version here and we’ll look it up later in the project.
Now that we have completed the configuration of the Ruby and Gem environment, let’s go through the funnel diagram again:
How to manage a project Gem environment using Bundler
Let’s see how Bundler can be used to lock down the Gem environment in a project so that the whole team can unify all versions of Ruby tools in the Gem environment. Thus avoiding file conflicts and unnecessary errors.
Below is a hierarchy diagram for Gem environments in the project. We can add a Gemfile description to the project to lock the Gem dependent environment in the current project.
The following describes the management mode of each layer and the actual operation method.
1. Initialize in the iOS projectBundler
The environment
First we have a iOS Demo project – GuaDemo:
$ ls -al
total 0
drwxr-xr-x 4 gua staff 128 Jun 10 14:47 .
drwxr-xr-x@ 52 gua staff 1664 Jun 10 14:47 ..
drwxr-xr-x 8 gua staff 256 Jun 10 14:47 GuaDemo
drwxr-xr-x@ 5 gua staff 160 Jun 10 14:47 GuaDemo.xcodeproj Copy the code
To initialize a Bundler environment (which automatically creates a Gemfile) :
$ bundle init
Writing new Gemfile to /Users/Gua/GuaDemo/Gemfile
Copy the code
In 2.Gemfile
Declaration used inCocoaPods
Version and install
After editing the Gemfile file, we will use CocoaPods 1.5.3 in our current environment and write the following content using Gemfile’s DSL:
# frozen_string_literal: true
source "https://rubygems.org"
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
# gem "rails" gem "cocoapods"."1.5.3" Copy the code
After writing, execute the bundle install:
$ bundle install
Fetching gem metadata from https://gems.ruby-china.com/.Resolving dependencies...
.Fetching cocoapods 1.5.3
Installing cocoapods 1.5.3 Bundle complete! 1 Gemfile dependency, 30 gems now installed. Copy the code
CocoaPods 1.5.3 is installed successfully and a gemfile. lock file is saved to store the dependencies.
3. Use the current environmentCocoaPods
Version Operating iOS project
At this point, we can check the Gem list of the current Bundler environment:
$ bundle exec gem list
*** LOCAL GEMS ***
activesupport (4.2.11.3)
atomos (0.1.3) bundler (2.1.4) CFPropertyList (3.0.2) claide (1.0.3) cocoapods (1.5.3) .Copy the code
It turns out that this list is much leaner than the global Gem list and only contains the base Gem dependencies and the Gem dependencies of CocoaPods. You can use CocoaPods 1.5.3 to perform pod operations (if you need to write a Podfile, of course). We’re all iOSer and I’m going to skip this.
$ bundle exec pod install
Analyzing dependencies
Downloading dependencies
Installing SnapKit (5.0.1)
Integrating client project
[!] Please close any current Xcode sessionsand use `GuaDemo.xcworkspace` for this project from now on. Sending stats Pod installation complete! There is 1 dependency from the Podfile and 1 total pod installed. Copy the code
Take a look at the podfile. lock file again:
cat Podfile.lock
PODS:
- SnapKit (5.0.1)
DEPENDENCIES:
- SnapKit (~> 5.0.0) SPEC REPOS: https://github.com/cocoapods/specs.git: - SnapKit SPEC CHECKSUMS: SnapKit: 97b92857e3df3a0c71833cce143274bf6ef8e5eb PODFILE CHECKSUM: 1a4b05aaf43554bc31c90f8dac5c2dc0490203e8 COCOAPODS: 1.5.3 Copy the code
I found that the version of CocoaPods I was using was actually 1.5.3. When we do not use the bundle exec to execute the prefix, we use the CocoaPods version in our system environment. We also demonstrated that the Gem environment in the project and the environment in the system can be isolated using Bundler.
conclusion
- From the perspective of the evolution of version management tools, it can be seen that the birth of CocoaPods did not happen overnight, but also constantly borrowed the advantages of other management tools, gradually developed. VCS tools from the early days
SVN
,Git
And subdivide itGit Submodule
, to the languagesPackage Manager
It’s always evolving. - although
CocoaPods
As a package management tool, it controls the various libraries that depend on iOS projects, but it also follows strict version control and iteration itself. Hopefully, you’ve learned from this article the importance of versioning. - Through the field
Bundler
The whole process of management engineering, learnedBundler
The basics and learning how to control a projectGem
Version information.
We will continue to work through CocoaPods from the toolchain to the details of our experience.
Knowledge points and problems sorting
Here are four questions to see if you have mastered the article, and if not, to bookmark it again:
PM
How do you version dependent libraries?Ruby
和RVM/rbenv
What is the relationship between?Gem
,Bundler
和CocaPods
What is the relationship between?- How to use
Bundler
To manage the projectGem
The environment? How to lock the inside of the projectCocoaPods
Version?
The resources
[1]
Learn more about CocoaPods: https://objccn.io/issue-6-4/
[2]
Semantic version: https://semver.org/
[3]
npmjs.com: https://www.npmjs.com/
[4]
Podfile guide: http://guides.cocoapods.org/syntax/podfile.html
[5]
Mater Repo CDN: http://blog.cocoapods.org/CocoaPods-1.7.2/
[6]
https://cdn.cocoapods.org: https://cdn.cocoapods.org
[7]
RubyGems
: https://rubygems.org/
[8]
Bundler
: https://bundler.io/
[9]
npm
: https://www.npmjs.com/
[10]
Gradle
: https://gradle.org/
[11]
RVM
: https://rvm.io/
[12]
rbenv
: https://github.com/rbenv/rbenv
[13]
Why rbenv: https://github.com/rbenv/rbenv/wiki/Why-rbenv%3F
[14]
RubyGems: https://rubygems.org/
[15]
Bundler: https://bundler.io/
[16]
Gemfile: https://bundler.io/v2.0/gemfile.html
This article is formatted using MDNICE