Xcode工程与Cocoapods

Xcode工程

一个常见的Xcode的工程如下:
xcodeProject

可以看到我们经常面对的一些地方,Workspace,Project,Scheme,Target

Target

Target是最小的编译单元,产物可在Product目录下看到,target可以为多种类型,比如framwork,extension,application等,每一个target对应着一个product产物,且可以独立配置。
target

Target配置

对于每一个Target,都有自己的独立配置,如上图中绿框所示,分别是

  • General:配置基础的信息,如Product的名字,bundle ID等信息。
  • Signing & Capailities:签名,能力(如推送能力)配置
  • Resource Tags:按需加载资源配置
  • Info:info文件配置,如权限配置等
  • Build Settings:配置Target,如指定使用的编译器,目标平台、编译参数、头文件搜索路径等
  • Build Phases:build阶段配置,如前置依赖、执行的脚本文件
  • Build Rules:配置自定义构建规则

要新建一个Target也很容易,在菜单栏点击File-> New ->Target或者点击TARGETS栏底部"+"即可,就会弹出要新建什么类型的Target,创建成功后,可以在Products文件夹中看到对应的Product。
createTarget

Target依赖

Target之间也可以有依赖,我们通过Cocoapods倒入进来的依赖库或者手动创建的Target都会自动添加到主Target的依赖中,需要注意的一点是通过cocoapods导入的依赖最终会被依赖为Pods_主工程名.framework:
targetDependency

Project

Project是Target的载体,也是Xcode可以直接打开的工程。Project无法被编译,所以对于一个Project而言,至少包含一个Target。
Project还可以包含其他的Project。
project
可以看出,一个Project可以包含多个Target。其中一个Project还有Build Settings等配置。如果Target中的Build Settings有相同的配置,则Target中的配置会继承或覆盖Project的配置

Workspace

Workspace就是Project容器。一个WorkSpace可以装载多个Project。当我们打开一个WorkSpace的时候,WorkSpace中的Project是相互可见的。 对xxx.xcworkspace文件单击右键,显示包内容,如下:
workspace
可以理解为它们之间的关系如下:
workspace-relation

Scheme

Scheme是一个理解为一个构建流程。定义了构建的Target,构建配置,以及测试配置。每一次构建,只能选择一个Scheme。点击下图位置即可配置和新建Scheme。
scheme
每一个Scheme都会对应一个Target。指明Target的各个构建流程的配置是怎样的,包括了Build、Run、Test、Profile、Analyze、Archive等操作每一个过程都可以单独配置。如下:
scheme-config

Settings

Build Setting是一个构建变量,指定了Target在构建中的信息。如指定Xcode传给编译器的变量。
除了上面在Project,Target中的Build Settings,我们也可以去自定义一个Build Settings。在Xcode工程中点击
File-> New -> File -> Configuration Settings File或者cmd+n选择Configuration文件。
在添加xcconfig文件的时候我们需要设置添加到哪个Target
config-file

Xcode工程在构建过程中,会按以下的顺序读取配置

  • .xcconfig文件中的配置
  • Target的Build Settings
  • Project的Build Settings
  • 平台的默认值

Cocoapods

Ruby工具链

CocoaPods其实是一个基于Ruby实现的库管理工具。先介绍一下Ruby常用的开发环境。

Ruby:一种开发语言,类似于JAVA,Python等
RVM:用于帮你安装Ruby环境,帮你管理多个Ruby环境,帮你管理你开发的每个Ruby应用使用机器上哪个Ruby环境
RubyGems是一个Ruby程序包管理器。它将一个Ruby应用程序打包到一个gem里,作为一个安装单元。
Gem:是封装起来的Ruby应用程序或代码库。
Gemfile:定义你的应用依赖哪些第三方包,bundle根据该配置去寻找这些包。
Bundler:是管理 Gem 依赖的工具。在配置文件Gemfile里说明你的应用依赖哪些第三方包,他自动帮你下载安装多个包,并且会下载这些包依赖的包
ruby_toolchain

对于CocoaPods,其实也是一个Gem。所以我们可以通过添加一个Gemfile文件为项目指定CocoaPods版本。

CocoaPods也借鉴了这种模式。结合上方设计如下:
cocoapods_structure

CocoaPods架构

CocoaPods其实是一个架构设计清晰的框架,将功能模块一个个划分。概览设计如下:
cocoapods_infra

  • CALide:负责处理在终端输入的命令,如pod init,将终端命令转换成需要执行的ruby代码

  • CocoaPods-core: 负责解析DSL模版,也就是我们的Podfile,.podSpec文件。我们的Podfile文件中编写的内容其实是Ruby,可以通过eval特性将Podfile中字符串解析成Ruby代码。

  • CocoaPods-Downloader: 负责下载源码。经过解析Podfile后中得到Ruby代码,会将每一个依赖的存入到数组,然后把这些代码下载下来
    我们可以通过pod init帮忙生成一个Podfile文件,配置依赖后运行pod install会生成一个worksapce文件和一个Pods文件夹,其中包含一个名为Pods的Project:
    pods

  • XcodeProj: 负责操作Xcode工程。下载完代码以后生成Pods工程和WorkSpace,为依赖的库生成target,并根据库与库之间的关系为Target添加依赖。

  • Cocoapods-plugins: CocoaPods中部分功能以plugin的形式提供,可以通过 pod plugins installed获取已经安装的plugin。如下

目录的组成部分:

  • 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库(:path或者:podspec指定时)install时的podspec

一些重要的文件:

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。这样能保证拉下来的版本库是其他人一致的。

在Xcode中可以看到如下:
pods-targets
每一个通过pod引入的库,都会生成对应的Target,每个Target的产物是framework,如果没有标记use_frameworks,pod会生成.a产物。

pod install的时候,Pods目录下生成一个Manifest.lock文件,内容与.lock文件完全一致;在每次build工程的时候,会检查这两个文件是否一致。

lock-comparation

2.PodSpec
在每个Pod库的仓库中,都会有一个podspec文件,描述Pod库的版本、依赖等信息:
podspec

可以在podspec中指定当前库的依赖和依赖的版本

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里面有Pods-testLibs.framework。
pods_link

那么Pod库中的target编译顺序是如何决定?
打开workspace,选择Pods工程。从上图分析我们知道,主工程最终需要的是Pods-testLibs.framework。看看Pods-testLibs的Build Phases选项,从target依赖中可以看到其他三个target。
pods_dependencies

分析至此,我们可以知道这里的编译顺序是SnapKit、Kingfisher、FluentIcons、Pods-testLibs、testLibs(主工程target)。
接下来我们分析编译过程。SnapKit因为没有依赖,所以编译的时候header search path为空。
snapkit_header_path

再来看下Pods-testLibs,他有三个依赖,分别是SnapKit,FluentIcons,Kingfisher,所以在header search paths中需要设置这三个库的头文件路径
testlibs_header_path

编译的结果有4个frameworks(FluentIcons.framework,Kingfisher.framework,SnapKit.framework,Pods-testLib.framework),我们在看下Pod-testLib.framework中二进制文件的大小:
testlibs_product_size

从Pods-testLib的大小,我们可以知道Pods-testLib不是多个二进制的集合,仅仅是作为主工程的一个依赖,使得Pod库工程能先于主工程编译
那么,主工程编译的时候如何去找到Kingfisher的头文件和二进制文件?
从主工程的Search Paths我们可以看到,Framewok是有说明具体的位置;
testlib_search_path

这些信息是CocoaPod生成的一份xcconfig,里面的HEADER_SEARCH_PATHS和LIBRARY_SEARCH_PATHS会指明这两个地址
xcconfig

对于资源文件,CocoaPods 提供了一个名为 Pods-resources.sh 的 bash 脚本,该脚本在每次项目编译的时候都会执行,将第三方库的各种资源文件复制到目标目录中。
CocoaPods 通过一个名为 Pods.xcconfig 的文件来在编译时设置所有的依赖和参数。
在编译之前会检查pod的版本是否发生变化(manifest和.lock文件对比),以及执行一些自定义的脚本。
编译得到可执行文件后,会进行asset、storyboard等资源文件的处理,还会执行pod的脚本,把pod的资源复制过来。
全部准备就绪,就会生成符号表,包括二进制文件里面的符号。
最后进行签名、校验,得到.app文件

https://www.jianshu.com/p/07ddbd829efc