前言
Pod库是很重要的组成部分,大部分第三方库都是通过CocoaPod的方式引入和管理,同时项目中的部分功能也可以用Pod库来做模块化。 本文是对CocoaPod的一些探究。 XS项目中的Pod库是很重要的组成部分,目前阅读器模块正在进行SDK化,需要用Pod库来管理,同时未来会做一些模块化的功能,同样需要用Pod库来处理。 本文对CocoaPods的一些内容进行探究。
正文
CocoaPods是为iOS工程提供第三方依赖库管理的工具,用CocoaPods可以更方便地管理第三方库:把依赖库统一放在Pods工程中,同时让主工程依赖Pods工程。Pods工程的target是libPods-targetName.a静态库,主工程会依赖这个.a静态库。 (下面会详细剖析这个处理过程)
CocoaPods相比手动引入framework或者子工程依赖的方式,有两个便捷之处:
- 所有Pod库集中管理,版本更新只需Podfile配置文件;
- 依赖关系的自动解析;
同时CocoaPods的使用流程很简单:(假设已经安装CocoaPods) 1、在xcodeproj所在目录下,新建Podfile文件; 2、描述依赖信息,以demo为例,有AFNetworking和SDWebImage两个第三方库:
代码语言:javascript复制target 'LearnPod' do
pod 'AFNetworking'
pod 'SDWebImage'
end
3、打开命令行,执行pod install ; 4、打开生成xcworkspace,就可以继续开发;
一、Podfile的写法
1、普通的写法;
pod 'AFNetworking'
或者 pod 'AFNetworking', '3.2.1'
,前者是下载最新版本,后者是下载指定版本。
2、指向本地的代码分支;
代码语言:javascript复制pod 'AFNetworking', :path => '/Users/loyinglin/Documents/Learn/AFNetworking'
指向的本地目录要带有podspec文件。
3、指定远端的代码分支;
pod 'AFNetworking', :git => 'https://github.com/AFNetworking/AFNetworking.git', :branch => 'master'
指向的repo仓库要带有podspec文件。
4、针对特定的configurations用不同的依赖库
代码语言:javascript复制`pod 'AFNetworking', :configurations => ['Release']`
如上,只有Release的configurations生效;(同理,可以设置Debug)
5、一些其他的feature
优化pod install速度,可以进行依赖打平:将pod库的依赖库明确的写在Podfile,主端已经提供对应的工具。
代码语言:javascript复制`require "bd_pod_extentions"`
`bytedanceAnalyzeSpeed(true)`
`bd_use_app('toutiao','thirdParty','public')`
post install的脚本,修改安装后的Pod库工程中的target设置;同理,可以修改其他属性的设置。
代码语言:javascript复制post_install do |installer_representation|
installer_representation.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ONLY_ACTIVE_ARCH'] = 'NO'`
end
end
end
类似的还有pre_install的脚本;(但是install之前可能都没有pods_project,所以用处也比较少;具体的参数意义可自查,以pods_project为例)
puts只有在添加--verbose参数可以看到,Pod::UI.puts则是全文可见。
代码语言:javascript复制pre_install do |installer|
puts "pre install hook"
Pod::UI.puts "pre install hook puts"
end
Podfile还可以设置一些警告提示的去除,第一行是去掉pod install时候的警告信息,第二行是去掉build时候的警告信息。
代码语言:javascript复制# 去掉pod install时候的警告信息
install! 'cocoapods', :warn_for_multiple_pod_sources => false
inhibit_all_warnings
类似的,还有很多其他用ruby去实现的feature。
二、Pods目录
Pods目录是pod install之后CocoaPod生成的目录。
目录的组成部分:
- 1、Pods.xcodeproj,Pods库的工程;每个Pod库会对应其中某个target,每个target都会打包出来一个.a文件;
- 2、依赖库的文件目录;以SDWebImage为例,会有个SDWebImage目录存放文件;
- 3、manifest.lock,Pods目录中的Pod库版本信息;每次pod install的时候会检查manifest.lock和Podfile.lock的版本是否一致,不一致的则会更新;
- 4、Target Support Files、Headers、Local Podspecs目录等;Target Support Files里面是一些target的工程设置xcconifg以及脚本等,Headers里面有Public和Private的头文件目录,Local Podspecs是存放从本地Pod库install时的podspec;
三、CocoaPods的其他重要部分
1.Podfile.lock文件 pod install会解析依赖并生成Podfile.lock文件;如果Podfile.lock存在时执行pod install,则不会修改已经install的pod库。(注意,pod update则会忽视Podfile.lock进行依赖解析,最后重新install所有的Pod库,生成新的Podfile.lock) 在多人开发的项目中,Pods目录由于体积较大,往往不会放在Git仓库中,Podfile.lock文件则建议添加到Git仓库。当其他人修改Podfile时,pod install生成新的Podfile.lock文件也会同步到Git。这样能保证拉下来的版本库是其他人一致的。
实际开发中,也会通过依赖打平来避免多人协作的Pod版本不一致问题。 pod install的时候,Pods目录下生成一个Manifest.lock文件,内容与.lock文件完全一致;在每次build工程的时候,会检查这两个文件是否一致。
2、Pod库的podspec文件 在每个Pod库的仓库中,都会有一个podspec文件,描述Pod库的版本、依赖等信息。 如下,是一个普通的Pod库的podspec:
3、Pod库依赖解析 CocoaPod的依赖管理相对第三方库手动管理更加便捷。 在手动管理第三方库中,如果库A集成了库F,库B也集成了库F ,就会遇到库F符号冲突的问题,需要将库A/B和库F的代码分开,手动添加库F;后续如果库A/B版本有更新,也需要手动去处理。 而在CocoaPod依赖解析中,可以把每个Pod库都看成一个节点,Pod库的依赖是它的子节点; 依赖解析的过程,就是在一个有向图中找到一个拓扑序列。 一个合法的Podfile描述的应该是一个有向无环图,可以通过拓扑排序的方式,得到一个AOV网。 按照这个拓扑序列中的顶点次序,可以依次install所有的Pod库并且保证其依赖的库已经install。
有时候会陷入循环依赖的怪圈,就是因为在有向图中出现环,则无法通过算法得到一个拓扑排序。
四、Pods工程和主工程的关系
在实际的开发过程,容易知道Pods工程是先编译,编译完再执行主工程的编译;因为主工程的Linked Libraries里面有libPods-LearnPod.a的文件。(LearnPod是target的名字,下面的示例图都是用LearnPod作为target名)
那么Pod库中的target编译顺序是如何决定? 打开workspace,选择Pods工程。从上图分析我们知道,主工程最终需要的是libPods-LearnPod.a这一个静态库文件。 我们通常打包,最终名字都是target的名字;而静态库通常会在前面加上lib的前缀。所以libPods-LearnPod.a这个静态库的target名字应该是Pods-LearnPod。 从下图我们也可以确定,确实是在前面添加了lib的前缀。
看看Pods-LearnPod的Build Phases选项,从target依赖中可以看到其他两个target。
分析至此,我们可以知道这里的编译顺序是AFNetworking、SDWebImage、Pods-LearnPod、LeanPod(主工程target)。 接下来我们分析编译过程。AFNetworking因为没有依赖,所以编译的时候只需要知道自己的.h/.m文件。
对于Pods-LearnPod,其有两个依赖,分别是AFNetworking和SDWebImage;所以在Header Search Paths中需要设置这两个库的Public头文件地址。
编译的结果是3个.a文件(libPods-LearnPod.a、libAFNetworking.a、libSDWebImage.a),只有libPods-LearnPod.a是主工程的编译依赖。那么libPods-LearnPod.a是否为多个.a文件的集合?
从libPods-LearnPod.a的大小,我们可以知道libPods-LearnPod不是多个.a的集合,仅仅是作为主工程的一个依赖,使得Pod库工程能先于主工程编译。 那么,主工程编译的时候如何去找到AFNetworking的头文件和.a文件? 从主工程的Search Paths我们可以看到,Header是有说明具体的位置; 同时Library也有相对应的Paths,在对应的位置放着libAFNetworking.a文件;
这些信息是CocoaPod生成的一份xcconfig,里面的HEADER_SEARCH_PATHS和LIBRARY_SEARCH_PATHS会指明这两个地址。
对于资源文件,CocoaPods 提供了一个名为 Pods-resources.sh 的 bash 脚本,该脚本在每次项目编译的时候都会执行,将第三方库的各种资源文件复制到目标目录中。 CocoaPods 通过一个名为 Pods.xcconfig 的文件来在编译时设置所有的依赖和参数。 在编译之前会检查pod的版本是否发生变化(manifest和.lock文件对比),以及执行一些自定义的脚本。 Pod库的子target在指定armv7和arm64两个架构的时候,会分别编译生成armv7和arm64的.a文件;然后再进行一次合并操作,得到一个.a文件。 编译完成后进行链接,在armv7和arm64都指定时,会分别进行链接,最后合并得到可执行文件。 得到可执行文件后,会进行asset、storyboard等资源文件的处理;还会执行pod的脚本,把pod的资源复制过来。 全部准备就绪,就会生成符号表,包括.a文件里面的符号。 最后进行签名、校验,得到.app文件。
五、常用Pod指令
pod install,最常用的指令; pod update,更新repo并重新解析依赖; pod install --repo-update,类似pod update; pod install --no-repo-update,忽略Pod库更新,直接用本地repo进行install; pod update --no-repo-update,类似pod install; pod update AFNetworking,更新指定库;
以上所有指令都可以添加 --verbose ,查看更详细的信息; xcconfig在新增configuration之后,需要重新pod install,并修改xcconfig。
附录
CocoaPods使用总结 基于 CocoaPods 进行 iOS 开发 pod install vs. pod update CocoaPods 都做了什么?