CocoaPods
是IOS项目
的依赖管理工具,类似于Android
的gradle
,不过gradle不仅有依赖管理功能,还能负责构建。CocoaPods
只负责管理依赖,即对第三方库的依赖,像gradle一样支持传递依赖,即如果A依赖于B,B依赖C,我们在A工程里指出A依赖于B,CocoaPods
会自动为我们下载C,并在构建时链接C库。
IOS工程有3种库项目,framework,static library,meta library
,我们通常只使用前两种。我们在使用static library
库工程时,一般使用它编译出来的静态库libxxx.a
,以及对应的头文件,在写应用时,将这些文件拷贝到项目里,然后将静态库添加到链接的的依赖库路径里,并将头文件目录添加到头文件搜索目录中。而framework
库的依赖会简单很多,framework
是资源的集合,将静态库和其头文件包含在framework
目录里。framework
库类似于Android
工程的aar库。而static
library
类似于Android
工程的jar
包。
CocoaPods
同时支持static library
和framework
的依赖管理,下面介绍这两种情况下CocoaPods
是如何实现构建上的依赖的.
1. static library
先看一下使用CocoaPods
管理依赖前项目的文件结构
CardPlayer
├── CardPlayer
│ ├── CardPlayer
│ ├── CardPlayer.xcodeproj
│ ├── CardPlayerTests
│ └── CardPlayerUITests
├── exportOptions.plist
└── wehere-dev-cloud.mobileprovision
然后我们使用Pod
来管理依赖,编写的PodFile
如下所示:
project 'CardPlayer/CardPlayer.xcodeproj'
target 'CardPlayer' do
pod 'AFNetworking', '~> 1.0'
end
2. 文件结构的变化
然后使用pod install
,添加好依赖之后,项目的文件结构如下所示:
CardPlayer
├── CardPlayer
│ ├── CardPlayer
│ ├── CardPlayer.xcodeproj
│ ├── CardPlayerTests
│ └── CardPlayerUITests
├── CardPlayer.xcworkspace
│ └── contents.xcworkspacedata
├── PodFile
├── Podfile.lock
├── Pods
│ ├── AFNetworking
│ ├── Headers
│ ├── Manifest.lock
│ ├── Pods.xcodeproj
│ └── Target\ Support\ Files
├── exportOptions.plist
└── wehere-dev-cloud.mobileprovision
可以看到我们添加了如下文件
- PodFile 依赖描述文件
- Podfile.lock 当前安装的依赖库的版本
- CardPlayer.xcworkspace
- xcworkspace文件,使用CocoaPod管理依赖的项目,XCode只能使用workspace编译项目,如果还只打开以前的xcodeproj文件进行开发,编译会失败
- xcworkspace文件实际是一个文件夹,实际Workspace信息保存在contents.xcworkspacedata里,该文件的内容非常简单,实际上只指示它所使用的工程的文件目录
如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:CardPlayer/CardPlayer.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
-
Pods
目录
- 1.
Pods.xcodeproj
,Pods
工程,所有第三方库由Pods工程构建,每个第3方库对应Pods
工程的1个target
,并且这个工程还有1个Pods-Xxx的target,接下来在介绍工程时再详细介绍 - 2.
AFNetworking
每个第3方库,都会在Pods目录下有1个对应的目录 -
Headers
在Headers
下有两个目录,Private
和Public
,第3方库的私有头文件会在Private
目录下有对应的头文件,不过是1个软链接,链接到第3方库的头文件 第3方库的Pubic
头文件会在Public目录
下有对应的头文件,也是软链接
如下所示:
-
Headers/
├── Private
│ └── AFNetworking
│ ├── AFHTTPClient.h -> ../../../AFNetworking/AFNetworking/AFHTTPClient.h
│ ├── AFHTTPRequestOperation.h -> ../../../AFNetworking/AFNetworking/AFHTTPRequestOperation.h
│ ├── AFImageRequestOperation.h -> ../../../AFNetworking/AFNetworking/AFImageRequestOperation.h
│ ├── AFJSONRequestOperation.h -> ../../../AFNetworking/AFNetworking/AFJSONRequestOperation.h
│ ├── AFNetworkActivityIndicatorManager.h -> ../../../AFNetworking/AFNetworking/AFNetworkActivityIndicatorManager.h
│ ├── AFNetworking.h -> ../../../AFNetworking/AFNetworking/AFNetworking.h
│ ├── AFPropertyListRequestOperation.h -> ../../../AFNetworking/AFNetworking/AFPropertyListRequestOperation.h
│ ├── AFURLConnectionOperation.h -> ../../../AFNetworking/AFNetworking/AFURLConnectionOperation.h
│ ├── AFXMLRequestOperation.h -> ../../../AFNetworking/AFNetworking/AFXMLRequestOperation.h
│ └── UIImageView+AFNetworking.h -> ../../../AFNetworking/AFNetworking/UIImageView+AFNetworking.h
└── Public
└── AFNetworking
├── AFHTTPClient.h -> ../../../AFNetworking/AFNetworking/AFHTTPClient.h
├── AFHTTPRequestOperation.h -> ../../../AFNetworking/AFNetworking/AFHTTPRequestOperation.h
├── AFImageRequestOperation.h -> ../../../AFNetworking/AFNetworking/AFImageRequestOperation.h
├── AFJSONRequestOperation.h -> ../../../AFNetworking/AFNetworking/AFJSONRequestOperation.h
├── AFNetworkActivityIndicatorManager.h -> ../../../AFNetworking/AFNetworking/AFNetworkActivityIndicatorManager.h
├── AFNetworking.h -> ../../../AFNetworking/AFNetworking/AFNetworking.h
├── AFPropertyListRequestOperation.h -> ../../../AFNetworking/AFNetworking/AFPropertyListRequestOperation.h
├── AFURLConnectionOperation.h -> ../../../AFNetworking/AFNetworking/AFURLConnectionOperation.h
├── AFXMLRequestOperation.h -> ../../../AFNetworking/AFNetworking/AFXMLRequestOperation.h
└── UIImageView+AFNetworking.h -> ../../../AFNetworking/AFNetworking/UIImageView+AFNetworking.h
- 4.
Manifest.lock manifest
文件 描述第3方库对其它库的依赖
PODS:
- AFNetworking (4.3.4)
DEPENDENCIES:
- AFNetworking (~> 1.0)
SPEC CHECKSUMS:
AFNetworking: cf8e418e16f0c9c7e5c3150d019a3c679d015018
PODFILE CHECKSUM: 349872ccf0789fbe3fa2b0f912b1b5388eb5e1a9
COCOAPODS: 1.9.1
-
Target Support Files
支撑target
的文件
-
Target\ Support\ Files/
├── AFNetworking
│ ├── AFNetworking-dummy.m
│ ├── AFNetworking-prefix.pch
│ └── AFNetworking.xcconfig
└── Pods-CardPlayer
├── Pods-CardPlayer-acknowledgements.markdown
├── Pods-CardPlayer-acknowledgements.plist
├── Pods-CardPlayer-dummy.m
├── Pods-CardPlayer-frameworks.sh
├── Pods-CardPlayer-resources.sh
├── Pods-CardPlayer.debug.xcconfig
└── Pods-CardPlayer.release.xcconfig
在Target Support Files
目录下每1个第3方库都会有1个对应的文件夹,比如AFNetworking
,该目录下有一个空实现文件,也有预定义头文件用来优化头文件编译速度,还会有1个xcconfig
文件,该文件会在工程配置中使用,主要存放头文件搜索目录,链接的Flag
(比如链接哪些库)
在Target Support Files
目录下还会有1个Pods-XXX
的文件夹,该文件夹存放了第3方库声明文档markdown
文档和plist
文件,还有1个dummy
的空实现文件,还有debug
和release
各自对应的xcconfig
配置文件,另外还有2个脚本文件,Pods-XXX-frameworks.sh
脚本用于实现framework
库的链接,当依赖的第3方库是framework
形式才会用到该脚本,另外1个脚本文件:Pods-XXX-resources.sh
用于编译storyboard
类的资源文件或者拷贝*.xcassets
之类的资源文件
3. 工程结构的变化
引入`CocoaPods`管理依赖后,会新增`workspace`文件,新增的`workspace`文件会引用原有的应用主工程,还会引用新增的`Pods`工程。后续不能再直接打开原来的应用主工程进行编译,否则会失败。实际上是因为原来的应用主工程的配置现在也有了变化。下面分别介绍一下`Pods`工程以及主工程的变化。
Pods
工程
Pods
工程配置
Pods
工程会为每个依赖的第3方库定义1个Target
,还会定义1个Pods-Xxx
的target
,每个Target
会生成1个静态库,如下图所示:
Pods
工程会新建Debug
和Release
两个Configuration
,每个Configuration
会为不同的target设置不同的xcconfig
,xcconfig
指出了头文件查找目录,要链接的第3方库,链接目录等信息,如下图所示:AFNetworking.xcconfig
文件的内容如下所示:
CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/AFNetworking
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/AFNetworking" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/AFNetworking"
OTHER_LDFLAGS = -framework "CoreGraphics" -framework "MobileCoreServices" -framework "Security" -framework "SystemConfiguration"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/AFNetworking
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
上述内容说明了AFNetworking
编译时查找头文件的目录Header_SERACH_PATHS
,OTHER_LD_FLAGS
指明了要链接的framework
Pods-CardPlayer.debug.xcconfig
文件的内容如下所示:
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/AFNetworking"
LIBRARY_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/AFNetworking"
OTHER_CFLAGS = $(inherited) -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/AFNetworking"
OTHER_LDFLAGS = $(inherited) -ObjC -l"AFNetworking" -framework "CoreGraphics" -framework "MobileCoreServices" -framework "Security" -framework "SystemConfiguration"
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-CardPlayer.debug
文件中OTHER_LDFLAGS
说明了编译Pods
时需要链接AFNetworking
库,还需要链接其它framework
所以我们在Xcode
里能看到AFNetworking
依赖的framework
:
3.1 Pods
工程文件组织
IOS
工程在XCode
上看到的结构和文件系统的结构并不一致,在XCode
上看到的文件夹并不是物理的文件夹,而是叫做Group
,在组织IOS
工程时,会将逻辑关系较近的文件放在同一个Group
下。如下图所示:
可以看到
Group
的组织大概是以下形式:
Pods
├── Podfile # 指向根目录下的Podfile 说明依赖的第3方库
├── Frameworks # 文件系统并没有对应的目录 这只是1个虚拟的group 表示需要链接的frameowork
├── └── iOS # 文件系统并没有对应的目录 这只是1个虚拟的group 这里表示是ios需要链接的framework
├── └── Xxx.framework # 链接的frameowork列表
├── Pods # 虚拟的group 管理所有第3方库
│ └── AFNetwoking #AFNetworking库 虚拟group 对应文件系统Pods/AFNetworking/AFNetworking目录下的内容
│ ├── xxx.h #AFNetworking库的头文件 对应文件系统Pods/AFNetworking/AFNetworking目录下的所有头文件
│ ├── xxx.m #AFNetworking库的实现文件 对应文件系统Pods/AFNetworking/AFNetworking目录下的所有实现文件
│ └── Support Files # 虚拟group 支持文件 没有直接对应的文件系统目录,该group下的文件都属于目录: Pods/Target Support Files/AFNetworking/
│ ├── AFNetworking.xcconfig # AFNetworking编译的工程配置文件
│ ├── AFNetworking-prefix.pch # AFNetworking编译用的预编译头文件
│ └── AFNetworking-dummy.m # 空实现文件
├── Products # 虚拟group
│ ├── libAFNetworking.a # AFNetworking target将生成的静态库
│ └── libPods-CardPlayer.a # Pods-CardPlayer target将生成的静态库
└── Targets Support Files # 虚拟group 管理支持文件
└── Pods-CardPlayer # 虚拟group Pods-CardPlayer target
├── Pods-CardPlayer-acknowledgements.markdown # 协议说明文档
├── Pods-CardPlayer-acknowledgements.plist # 协议说明文档
├── Pods-CardPlayer-dummy.m # 空实现
├── Pods-CardPlayer-frameworks.sh # 安装framework的脚本
├── Pods-CardPlayer-resources.sh # 安装resource的脚本
├── Pods-CardPlayer.debug.xcconfig # debug configuration 的 配置文件
└── Pods-CardPlayer.release.xcconfig # release configuration 的 配置文件
3.2 主工程
引入CocoaPods
之后, 主工程的设置其实也会变化, 我们先看一下引入之前,主工程的Configuration
设置,如下图所示:
可以看到
Debug
和Release
的Configuration
没有设置任何配置文件,再看引入CocoaPods
之后,主工程的Configuration
如下图所示:可以看到采用
CocoaPods
之后,Debug Configuration
设置了配置文件Pods-CardPlayer.debug.xcconfig
文件,Release Configuration
则设置了配置文件Pods-CardPlayer.release.xcconfig
文件,这些配置文件指明了头文件的查找目录,要链接的第三方库
3.3 编译并链接第3方库的原理
3.3.1 头文件的查找
上一节里已经讲到主工程的Configuration
已经设置了配置文件,而这份配置文件里说明了头文件的查找目录:
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/AFNetworking"
OTHER_CFLAGS = $(inherited) -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/AFNetworking"
所以主工程可以引用第3方库的头文件,比如像这样:#import <AFNetworking/AFHTTPClient.h>
3.3.2 如何链接库
配置文件同样说明了链接库的查找目录以及要链接的库:
LIBRARY_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/AFNetworking"
OTHER_LDFLAGS = $(inherited) -ObjC -l"AFNetworking" -framework "CoreGraphics" -framework "MobileCoreServices" -framework "Security" -framework "SystemConfiguration"
而在我们主工程的main target
还会添加对libPods-CardPlayer.a
的链接,如下图所
示:
3.3.3 编译顺序
我们的主工程的`main target`显示指出了需要链接库`libPods-CardPlayer.a`,而`libPods-CardPlayer.a`由`target Pods-CardPlayer`产生,所以主工程的`main target`将会隐式依赖于`target Pods-CardPlayer`,而在`target Pods-CardPlayer`的配置中,显示指出了依赖对第三方库对应的`target`的依赖,如下所示:
所以
main target
->target Pods-CardPlayer
-> 第3方库对应的target
因为存在上述依赖关系,所以能保证编译顺序,保证编译链接都不会有问题
4.framework
如果我们在PodFile
设置了use_frameworks!
,则第3方库使用Framework
形式的库,PodFile
的内容如下所示:
project 'CardPlayer/CardPlayer.xcodeproj'
use_frameworks!
target 'CardPlayer' do
pod 'AFNetworking', '~> 1.0'
end
framework
这类型的库
和static library
比较类似,在文件结构上没什么太大变化,都是新增了Pods
工程,和管理Pods
工程及原主工程的workspace
,但是Pods
工程设置的target
的类型都是framework
,而不是static library
,而主工程对Pods的依赖,也不再是依赖libPods-CardPlayer.a
,而是Pods_CardPlayer.framework
。如下图所示:
另外编译配置文件也有一些不同:
AFNetworking.xcconfig
文件如下所示:
CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/AFNetworking
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public"
OTHER_LDFLAGS = -framework "CoreGraphics" -framework "MobileCoreServices" -framework "Security" -framework "SystemConfiguration"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/AFNetworking
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
而Pods-CardPlayer.debug.xcconfig
文件的内容如下所示:
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/AFNetworking"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/AFNetworking/AFNetworking.framework/Headers"
OTHER_LDFLAGS = $(inherited) -framework "AFNetworking"
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
使用framework
形式的库之后,Pods-CardPlayer-frameworks.sh
脚本也有一些不同
...
f [[ "$CONFIGURATION" == "Debug" ]]; then
install_framework "${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework"
fi
if [[ "$CONFIGURATION" == "Release" ]]; then
install_framework "${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework"
fi
if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
wait
fi
编译framework
后,它会将AFNetworking.framework
安装到产品编译目录下,这样才能在运行时链接该framework
而我们的主工程的main target
配置Build Phases
有一项安装pod
的framework
,会调用Pod-CardPlayer-frameworks.sh
,所以能保证正确安装framework
,如下图所示: