CocoaPods Podspec 解析原理
在 CocoaPods 中,podspec 文件主要用于描述一个 pod 库的基本信息,包括:名称、版本、源、依赖等等。本文,我们来介绍一下 CocoaPods-Core 中另一个重要的部分——podspec。
Podspec 初始化
当执行 pod install
时,CocoaPods 会从本地的 pod repo
中查找与指定的 pod 命名和版本所对应的 podspec 文件。如果没有找到,那么
CocoaPods 会更新 pod repo,这背后有着一套复杂的管理机制,关于 podspec
管理机制下一篇文章我们将进行介绍。如果找到了,那么 CocoaPods 会对
podspec 文件进行初始化。
Source
类提供了一个
specification(name, version)
方法支持加载指定命名和版本的
podspec,如下所示:
1 | # source.rb |
CocoaPods-Core
使用
Specification
/Spec
类表示
podspec,其提供了一个类方法 self.from_file
支持 podspec
初始化,如下所示:
1 | # specification.rb |
self.from_file
会对文件路径进行校验,并读取 podspec
文件内容,进行 UTF-8 编码后,调用 self.from_string
方法进行初始化。self.from_string
方法实现如下所示:
1 | # specification.rb |
从 self.from_string
方法的实现可以看出,podspec
支持两种文件类型,分别是:.podspec
和
.json
。
对于 .podspec
文件类型,podspec 文件中定义的 ruby
代码,因此直接调用 ::Pod._eval_podspec
方法执行 ruby
代码,从而完成初始化。
对于 .json
文件类型,specification
类通过混入(mixin)实现了 from_json
方法的
JSONSupport
模块,从而完成初始化。from_json
的内部实现如下所示:
1 | # specification/json.rb |
这里,self.from_json
将 json 格式的 podspec 转换成了
hash,然后以此为参数调用 self.from_hash
方法进行 podspec
初始化。
self.from_hash
中初始化一个 Specification
对象,并对其相关属性进行设置。这里,最关键的就是 将 hash 赋值给
Specification
对象的 attribuets_hash
属性,从而使 Specification
对象拥有了 podspec
所描述的所有信息。
Specification 树构建
在上一节中,self.from_hash
方法完成了对 podspec
的初始化,需要注意其内部还调用了 self.subspecs_from_hash
方法,该方法的实现如下所示: 1
2
3
4
5
6
7
8
9
10# specification/json.rb
def self.subspecs_from_hash(spec, subspecs, test_specification, app_specification)
return [] if subspecs.nil?
# 每一个 subspec 对应初始化一个 Specification 对象,并构建一个 Specification 树
subspecs.map do |s_hash|
Specification.from_hash(s_hash, spec,
:test_specification => test_specification,
:app_specification => app_specification)
end
end
方法内部对 Specification
的每一个 subspec 初始化一个
Specification
对象,并绑定父子关系,从而构建一棵
Specification
树。以 AFNetworking 为例,根据其 podspec
构建的 Specification
树如下所示。
Specification 核心
前面,我们介绍了通过 podspec 如何构建一棵 Specification
树,下面我们来看看 Specification
的核心结构,如下所示:
1 | # specification.rb |
类似于 Podfile
,Specification
同样使用
attributes_hash
记录配置信息。此外,对于
subspec
,会有一个 parent
属性指向其所属的
Specification
父节点。
当看到了 Specification
类的核心实现后,我们可能会想到上一篇文章中所提到的
TargetDefinition
类。两者都构建了一个树结构,两者之间是否存在什么关系?
在 Xcode 中,target
作为一个最小的可编译单元,它编译后的产物为链接库或 framework。在
CocoaPods 中,target 则由 Specification
进行描述,最终转换成 TargetDefinition
,即 Xcode
Target。
在 Xcode 中,一个 target 可以依赖其他 target 进行构建。对应,在 CocoaPods 中,一个 spec 可以依赖其他 subspec,从而描述整个构建关系。
subspec 可以单独作为依赖被引入到项目中,其包含以下特点:
- 在未指定
default_subspec
的情况下,spec 的全部 subspec 都将作为依赖被引入项目 - subspec 会主动继承父节点 spec 所定义的
attributes_hash
- subspec 可以指定自己的源代码、资源文件、编译配置、依赖等
- 同一 spec 内部的 subspec 之间可以存在依赖关系
- 每个 subspec 在
pod push
时都需要 lint 通过
Specification DSL
与 Podfile 类似,Podspec 也定义了一系列 DSL,DSL 基本原理是:使用一个哈希表存储配置项,并提供一系列方法对应操作不同的配置项。具体过程包括以下几个步骤:
- 使用
attribute
或root_attribute
方法声明属性和配置 - 属性和配置直接存储至类属性
Self.attributes
- 遍历
Self.attributes
,为每个属性名称定义动态 setter 方法 - 属性和配置转发存储至实例属性
attributes_hash
如下所示,Specification::DSL::AttributeSupport
模块定义了上述流程中几个关键的属性和方法:Self.attributes
、root_attribute
、attribute
、store_attribute
。
1 | # specification/dsl/attribute_support.rb |
如下所示,Specification
类中定义了动态方法和存储转发逻辑。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25# specification.rb
# 将属性存储至 attributes_hash
def store_attribute(name, value, platform_name = nil)
name = name.to_s
value = Specification.convert_keys_to_string(value) if value.is_a?(Hash)
value = value.strip_heredoc.strip if value.respond_to?(:strip_heredoc)
if platform_name
platform_name = platform_name.to_s
attributes_hash[platform_name] ||= {}
attributes_hash[platform_name][name] = value
else
attributes_hash[name] = value
end
end
# Spec 类加载时遍历 attributes 动态生成 setter 方法
DSL.attributes.values.each do |a|
define_method(a.writer_name) do |value|
store_attribute(a.name, value)
end
if a.writer_singular_form
alias_method(a.writer_singular_form, a.writer_name)
end
end
Podspec DSL 根据功能可以分为以下 6 大类,具体 DSL 定义可以参考官方文档——传送门。
- Root Specification:基本配置,用于描述名称、版本号等
- Platform:指定平台约束
- Build
Settings:构建所需的依赖配置,如:
dependency
、frameworks
- File Patterns:文件管理,如:源码文件、资源文件、内嵌 frameworks、内嵌 libraries
- Subspecs:podspec 的子模块,一个子模块以一个 target 为单元进行构建
- Multi-Platform Support:为不同的平台执行不同的资源文件
总结
整体而言,Podspec 的解析原理与 Podfile 差不多,都是定义一组 DSL,通过 DSL 方法将配置的属性保存在一个对象的哈希表中。与此同时,构建一个树从而建立相互之间的依赖关系,所有的配置信息都保存在一棵对象树中。