[iOS] CocoaPods - pod install的背后

0. 简介

CocoaPods是用Ruby写的,并由若干个Ruby包(gems)构成的。在解析整合过程中,最重要的几个gems分别是:CocoaPods/CocoaPods, CocoaPods/Core, 和 CocoaPods/Xcodeproj

  • CocoaPods/CocoaPod
    面向用户的组件,每当执行一个pod命令时,这个组件都会被激活。这个组件包括了所有使用CocoaPods涉及到的功能,并且还能通过调用其它的gems来执行任务。

  • CocoaPods/Core
    Core 组件支持与 CocoaPods 相关文件的处理,文件主要是 Podfilepodspecs

  • Podfile
    Podfile 是一个文件,用于定义项目所需要使用的第三方库

  • Podspec
    .podspec 也是一个文件,该文件描述了一个库是怎样被添加到工程中的。它支持的功能有:列出源文件、framework、编译选项和某个库所需要的依赖等。

  • CocoaPods/Xcodeproj
    这个 gem 组件负责所有工程文件的整合。它能够对创建并修改 .xcodeproj.xcworkspace 文件。它也可以作为单独的一个 gem 包使用。如果你想要写一个脚本来方便的修改工程文件,那么可以使用这个 gem。

1. 项目结构

1.1 新建一个不带测试模块的OC项目

目录结构如下:

├── HTDemo
│   ├── AppDelegate.h
│   ├── AppDelegate.m
│   ├── Assets.xcassets
│   ├── Base.lproj
│   ├── Info.plist
│   ├── SceneDelegate.h
│   ├── SceneDelegate.m
│   ├── ViewController.h
│   ├── ViewController.m
│   └── main.m
└── HTDemo.xcodeproj
    ├── project.pbxproj
    ├── project.xcworkspace
    └── xcuserdata

1.2 新建Podfile模板

然后执行pod init创建一个Podfile模板,在里面引入SDWebImageMasonry,并且指定了Masonry的版本号:

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'HTDemo' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # Pods for HTDemo
  pod 'SDWebImage'
  pod 'Masonry', '~> 1.1.0'

end

1.3 使用CocoaPods之后的项目结构

执行pod install之后,就会将这两个库引入到项目中,此时项目目录如下:

├── HTDemo
│   ├── AppDelegate.h
│   ├── AppDelegate.m
│   ├── Assets.xcassets
│   ├── Base.lproj
│   ├── Info.plist
│   ├── SceneDelegate.h
│   ├── SceneDelegate.m
│   ├── ViewController.h
│   ├── ViewController.m
│   └── main.m
├── HTDemo.xcodeproj
│   ├── project.pbxproj
│   ├── project.xcworkspace
│   └── xcuserdata
├── HTDemo.xcworkspace
│   └── contents.xcworkspacedata
├── Podfile
├── Podfile.lock
└── Pods
    ├── Headers
    ├── Local\ Podspecs
    ├── Manifest.lock
    ├── Masonry
    ├── Pods.xcodeproj
    ├── SDWebImage
    └── Target\ Support\ Files

除了使用pod init创建的Podfile,其余的HTDemo.xcworkspacePodfile.lockPods都是由pod install之后生成的。

2. CocoaPods安装的内容

2.1 .xcworkspace文件

xcworkspace是一个项目容器,当有多个project需要相互依赖时可以用xcworkspace将它们组织起来。该文件下包含一个contents.xcworkspacedata的文件,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<Workspace
   version = "1.0">
   <FileRef
      location = "group:HTDemo.xcodeproj">
   </FileRef>
   <FileRef
      location = "group:Pods/Pods.xcodeproj">
   </FileRef>
</Workspace>

使用xml格式将依赖包含在标签内。

cocoapods在首次安装三方库时会生成一个叫Pods.xcodeproj的project管理三方库,然后将该project和主项目的project通过workspace进行管理。这样就可以在主工程里引入三方库了,而且三方库由Pods.xcodeproj统一管理,不会对我们原项目产生任何干扰。

2.2 Podfile.lock

Podfile.lock文件内容如下:

PODS:
  - Masonry (1.1.0)
  - SDWebImage (5.11.1):
    - SDWebImage/Core (= 5.11.1)
  - SDWebImage/Core (5.11.1)

DEPENDENCIES:
  - Masonry (~> 1.1.0)
  - SDWebImage

SPEC REPOS:
  trunk:
    - Masonry
    - SDWebImage

SPEC CHECKSUMS:
  Masonry: 678fab65091a9290e40e2832a55e7ab731aad201
  SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d

PODFILE CHECKSUM: 5891c4ea6e31c72930f38c52e6b5a23e24b702de

COCOAPODS: 1.10.1

因为Podfile文件里可以不指定版本号,而版本信息又很重要,于是就有了Podfile.lock,它里面记录完整的版本信息和依赖关系,下面会具体介绍。

2.2.1 PODS
PODS:
  - Masonry (1.1.0)
  - SDWebImage (5.11.1):
    - SDWebImage/Core (= 5.11.1)
  - SDWebImage/Core (5.11.1)

PODS中显示了引用的第三方库的具体版本号,pod是通过各个三方库的.podspec文件找到对应依赖的。

2.2.2 DEPENDENCIES

DEPENDENCIES为pod库的描述信息,这里内容是同Podfile里的写法。因为我们指定了Masonry的版本号,并没有指定SDWebImage的版本号,所以这里内容也是一样的。

2.2.3 SPEC REPOS
SPEC REPOS:
  trunk:
    - Masonry
    - SDWebImage

这里描述的是仓库信息,即安装了哪些三方库,他们来自于哪个仓库。

2.2.4 SPEC CHECKSUM

这里描述的是各个三方库的校验和,校验和的算法是对当前安装版本的三方库的podspec文件求SHA1。podspec文件发生变化意味着版本信息发生了变化,就需要重新同步代码。

2.2.5 COCOAPODS: 1.9.3

这个代表当前使用的CocoaPod版本号,远程版本管理应该要保证大家使用的pod版本号一致。

2.3 Pods

2.3.1 Manifest.lock

Manifest.lockPodfile.lock的副本,它是在Pods目录里面。它的作用是这样的,我们通常是不把Pods文件放到版本管理里面,而把Podfile.lock放到版本管理里面。这时对于拉取代码之后是否需要更新pod,就可以通过对比本地的Manifest.lock和远程Podfile.lock是否相同即可。

2.3.2 Targets Support Files

Pods安装的依赖是这样的组织形式:

截屏2021-12-15 下午9.17.32.png

这个Pods的Project下面有三个Targets,其中两个是依赖库,Pods-HTDemo是关联两个库的Framework。

2.3.2.1 Pods-HTDemo Framework

这个Framework,被用于工程项目的引用依赖,但是并不会打进包里(Do Not Embed),如下图所示:


截屏2021-12-16 上午11.10.25.png

这个工程下的配置文件:


截屏2021-12-16 上午11.11.12.png
  • Framework文件这里还包含了用于管理Module的modulemapumbrella.h文件。modulemap是对Module的声明文件,制作Framework我们总是需要该文件,它的内容如下:
framework module Pods_HTDemo {
  umbrella header "Pods-HTDemo-umbrella.h"

  export *
  module * { export * }
}

其指向了一个umbrella的头文件,这是制作Framework必须的头文件,modulemapumbrella.h会在创建Module时自动生成,不建议手动修改其关系。

  • dummy.m文件
    这是一个空的.m文件:
#import <Foundation/Foundation.h>
@interface PodsDummy_Pods_HTDemo : NSObject
@end
@implementation PodsDummy_Pods_HTDemo
@end

Xcode的编译是依赖.m文件的,如果一个库里没有.m文件,将不会被编译,为了防止这种情况就会在每个库里增加一个空的.m文件。有的时候在三方库的包里也会包含一个dummy文件。

dymmy意为假,说明是占位用的。

  • xcconfig文件
    xcconfig文件是Build Setting配置项的文件形式,它的优先级 > Xcode的Build Setting,pod会自动生成debugrelease两个环境下的xcconfig文件,并且cocoapods会修改我们的工程配置,让这两个xcconfig文件生效:
    image.png

来看一个pod生成的debug模式下的xcconfig文件,内容如下:

CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers"
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_LDFLAGS = $(inherited) -framework "Foundation" -framework "ImageIO" -framework "Masonry" -framework "SDWebImage" -framework "UIKit"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
PODS_ROOT = ${SRCROOT}/Pods
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES

如果修改里面的配置,结果会同步到工程target的Build Setting配置中。
上面文件中配置FRAMEWORK_SEARCH_PATHSHEADER_SEARCH_PATHS 用于帮助我们在项目中查找第三方库的HEADER_SEARCH_PATHSHEADER_SEARCH_PATHS,在工程target的Build Setting中可以看到,如下图所示:

image.png

一些自定义的参数,会体现到target的Build Setting最后的User-Defined中:

image.png

2.3.2.2 三方库 Framework

每个三方库也都有一些配置文件,文件格式基本一致,下图是Masonry的配置文件,Xcode中Pods > Pods > Target Support Files > Masonry对应的文件就是该内容:

截屏2021-12-16 下午2.24.03.png

如果三方库也需要依赖别的库时,是如何找到依赖的这个库呢? 也是需要在Build Setting里进行配置,所以这也是Framework里xcconfig文件的作用。

3. Build Phases

这里是编译阶段配置的地方,当第一次pod install成功之后,这里会多一些[CP](CocoaPods的缩写)开头的配置项,它们都是由CocoaPods添加的脚本内容,执行顺序,从上到下,如下图所示:

image.png

3.1 [CP]Check Pods Manifest.lock

image.png

可以看到,这里执行了一个脚本:对比Podfile.lockManifest.lock文件(判断远端代码和本地依赖是否一致)如果不同,会提示让你重新pod install。终于知道这个报错是在哪里写的了。

3.2 [CP] Embed Pods Frameworks

通过名字可以知道,这一步是嵌入第三方的Frameworks,里面会调用一个脚本文件:"${PODS_ROOT}/Target Support Files/Pods-HTDemo/Pods-HTDemo-frameworks.sh",同时也有个输入和输出文件,如下:

截屏2021-12-16 下午2.36.37.png

Pods-HTDemo-frameworks.sh这个脚本文件主要的作用就是将三方库的frameworks导入到编译生成的的xxx.app/Frameworks目录下。

4. pod install 详细内容

之前在使用pod install命令的时候,从来没有关注过这个命令执行的详细内容。可以在这个命令后面加上--verbose,来查看这个命令执行的详细信息:

# 分析依赖
Analyzing dependencies

# 检查目标CPU指令集
Inspecting targets to integrate
  Using `ARCHS` setting to build architectures of target `Pods-HTDemo`: (``)

# 查找Podfile文件的改变
Finding Podfile changes
  - Masonry
  - SDWebImage

# 解析Podfile文件的依赖项
Resolving dependencies of `Podfile`
  CDN: trunk Relative path: CocoaPods-version.yml exists! Returning local
  because checking is only perfomed in repo update
  CDN: trunk Relative path: all_pods_versions_1_1_7.txt exists! Returning local
  because checking is only perfomed in repo update
  CDN: trunk Relative path:
  Specs/1/1/7/SDWebImage/5.11.1/SDWebImage.podspec.json exists! Returning local
  because checking is only perfomed in repo update
  CDN: trunk Relative path: all_pods_versions_a_a_4.txt exists! Returning local
  because checking is only perfomed in repo update
  CDN: trunk Relative path: Specs/a/a/4/Masonry/1.1.0/Masonry.podspec.json
  exists! Returning local because checking is only perfomed in repo update
  CDN: trunk Relative path:
  Specs/1/1/7/SDWebImage/5.11.1/SDWebImage.podspec.json exists! Returning local
  because checking is only perfomed in repo update
  CDN: trunk Relative path:
  Specs/1/1/7/SDWebImage/5.11.1/SDWebImage.podspec.json exists! Returning local
  because checking is only perfomed in repo update
  CDN: trunk Relative path: Specs/a/a/4/Masonry/1.1.0/Masonry.podspec.json
  exists! Returning local because checking is only perfomed in repo update

# 将上面解析的规范与Manifest.lock文件进行比较
Comparing resolved specification to the sandbox manifest
  - Masonry
  - SDWebImage

# 下载依赖项
Downloading dependencies

-> Using Masonry (1.1.0)

-> Using SDWebImage (5.11.1) 
  - Running pre install hooks # 在执行install时,做了一些操作

# 生成Pods project
Generating Pods project 
  - Creating Pods project
  - Installing files into Pods project
    - Adding source files
    - Adding frameworks
    - Adding libraries
    - Adding resources
    - Linking headers
# 导入所有的Target
  - Installing Pod Targets
    - Installing target `Masonry` iOS 8.0
      - Generating module map file at `Pods/Target Support
      Files/Masonry/Masonry.modulemap`
      - Generating umbrella header at `Pods/Target Support
      Files/Masonry/Masonry-umbrella.h`
      - Generating Info.plist file at `Pods/Target Support
      Files/Masonry/Masonry-Info.plist`
      - Generating dummy source at `Pods/Target Support
      Files/Masonry/Masonry-dummy.m`
    - Installing target `SDWebImage` iOS 9.0
      - Generating module map file at `Pods/Target Support
      Files/SDWebImage/SDWebImage.modulemap`
      - Generating umbrella header at `Pods/Target Support
      Files/SDWebImage/SDWebImage-umbrella.h`
      - Generating Info.plist file at `Pods/Target Support
      Files/SDWebImage/SDWebImage-Info.plist`
      - Generating dummy source at `Pods/Target Support
      Files/SDWebImage/SDWebImage-dummy.m`
  - Installing Aggregate Targets
    - Installing target `Pods-HTDemo` iOS 15.0
      - Generating Info.plist file at `Pods/Target Support
      Files/Pods-HTDemo/Pods-HTDemo-Info.plist`
      - Generating module map file at `Pods/Target Support
      Files/Pods-HTDemo/Pods-HTDemo.modulemap`
      - Generating umbrella header at `Pods/Target Support
      Files/Pods-HTDemo/Pods-HTDemo-umbrella.h`
      - Generating dummy source at `Pods/Target Support
      Files/Pods-HTDemo/Pods-HTDemo-dummy.m`
  - Generating deterministic UUIDs
  - Stabilizing target UUIDs
  - Running post install hooks
  - Writing Xcode project file to `Pods/Pods.xcodeproj`
  Cleaning up sandbox directory

Integrating client project

Integrating target `Pods-HTDemo` (`HTDemo.xcodeproj` project)
  - Running post integrate hooks
  - Writing Lockfile in `Podfile.lock`
  - Writing Manifest in `Pods/Manifest.lock`
  CDN: trunk Relative path: CocoaPods-version.yml exists! Returning local
  because checking is only perfomed in repo update

-> Pod installation complete! There are 2 dependencies from the Podfile and 2 total pods installed.

主要有以下几步:

  • 读取podfile文件,解析依赖项
    cocoapods会分析podfile中声明的依赖项,并从本地索引库specs中查找对应的三方库的Masonry.podspec.json文件(如果要求的版本在本地索引库查不到,就会报错提示你需要先进行pod repo update

  • 和Manifest.lock文件做对比
    检查要导入的三方库版本和Manifest.lock中的版本是否一致

  • 加载源文件
    cocoapods会按照xxx.podspec.json和缓存文件的信息,将第三方库的源文件下载到Pods目录中

  • 生成 Pods.xcodeproj
    每次 pod install 执行,如果检测到改动时,CocoaPods 会利用 Xcodeproj gem组件对 Pods.xcodeproj进行更新。如果该文件不存在,则用默认配置生成。否则,会将已有的配置项加载至内存中。

  • 导入第三方库
    当 CocoaPods 往工程中添加一个第三方库时,不仅仅是添加代码这么简单,还会添加很多内容。由于每个第三方库有不同的 target,因此对于每个库,都会有几个文件需要添加,每个 target 都需要:
    a.一个包含编译选项的 .xcconfig 文件
    b.一个同时包含编译设置和 CocoaPods 默认配置的私有 .xcconfig 文件
    c.一个编译所必须的 prefix.pch 文件
    d.另一个编译必须的文件 dummy.m

一旦每个 pod 的 target 完成了上面的内容,整个 Pods target 就会被创建。这增加了相同文件的同时,还增加了另外几个文件。如果源码中包含有资源 bundle,将这个 bundle 添加至程序 target 的指令将被添加到 Pods-Resources.sh文件中。还有一个名为 Pods-environment.h 的文件,文件中包含了一些宏,这些宏可以用来检查某个组件是否来自 pod。最后,将生成两个许可文件,一个是 plist,另一个是 markdown,这两个文件用于给最终用户查阅相关许可信息。

  • 写入到磁盘
    直到现在,许多工作都是在内存中进行的。为了让这些成果能被重复利用,我们需要将所有的结果保存到一个文件中。所以 Pods.xcodeproj文件被写入磁盘,另外两个非常重要的文件:Podfile.lockManifest.lock 都将被写入磁盘。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,056评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,842评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,938评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,296评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,292评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,413评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,824评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,493评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,686评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,502评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,553评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,281评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,820评论 3 305
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,873评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,109评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,699评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,257评论 2 341