podspec

Podspec 文件

Podspec 是什么

Podspec 是用于描述一个 pod 库详细信息的文件,包括从哪里获取代码、使用哪些文件、应用什么编译设置以及名字、版本、描述等其他原始数据。

PodSpec 示例:

1
2
3
4
5
6
7
8
9
10
11
12
Pod::Spec.new do |spec|
spec.name = 'Reachability'
spec.version = '3.1.0'
spec.license = { :type => 'BSD' }
spec.homepage = 'https://github.com/tonymillion/Reachability'
spec.authors = { 'Tony Million' => 'tonymillion@gmail.com' }
spec.summary = 'ARC and GCD Compatible Reachability Class for iOS and macOS.'
spec.source = { :git => 'https://github.com/tonymillion/Reachability.git', :tag => spec.version.to_s }
spec.source_files = 'Reachability.h,m'
spec.framework = 'SystemConfiguration'
spec.requires_arc = true
end

可以通过命令 pod spec create [NAME|https://github.com/USER/REPO] 来单独创建一个 PodSpec 文件。

一般情况下,写一个全新的 pod 库时不需要单独创建 Podspec 文件, 在使用 pod lib create 命令创建 pod 库时同时会创建同名的 Podspec 文件。发布现有代码时,直接创建一个 PodSpec 即可。

Podspec 支持的文件格式有两种:.podspec.json。而 .podspec 的本质是 Ruby 文件。常用的是 .podspec.

Podspec 语法参考

一个完整的 Podspec 文件包含以下部分:

图片引用自 https://www.desgard.com/2020/10/12/cocoapods-story-5.html

Root specification

必填:

name version authors licence homepage source summary
名字 版本 作者 许可证 主页 源码地址 简短描述

选填:

swift_versions

支持的 swift 版本

1
2
3
4
spec.swift_versions = ['3.0']
spec.swift_versions = ['3.0', '4.0', '4.2']
spec.swift_version = '3.0',
spec.swift_version = '3.0', '4.0'

‘4’ 会被当做 ‘4.0’, 而不会是 ‘4.1’ 或 ‘4.2’。swift 编译器主要接受主版本号有时也会接受次版本号。虽然 cocoapods 允许指定次版本号,但不保证编译器会接受。

cocoapods_version

对 cocoapods 版本的要求

1
spec.cocoapods_version = '>= 1.9.0'

prepare_command

在 pod 库被下载之后运行的 bash 脚本,可以创建、修改、删除下载的文件,脚本在 spec 文件中其他所有字段信息被收集之前被运行。如果使用 :path 参数,次命令不会执行。

注意:此命令只有在 某个版本的 pod 第一次被下载时才会执行。

1
2
3
4
5
s.prepare_command = <<-CMD
echo 'here is prepare_command'
CMD

s.prepare_command = 'ruby prepare_command.rb'

应用示例:创建 module.modulemap,项目中继承 Umeng 时, swift 工程需要创建桥接文件,因为缺少 module.modulemap 文件。可以使用 prepare_command 在集成前创建该文件,即可实现 import

https://blog.indigo.codes/2017/05/01/dev-on-pod

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
26
27
28
Pod::Spec.new do |s|
s.name = "UMengAnalytics-NO-IDFA"
s.version = "4.2.5"
s.summary = "UMeng's unofficial Analytics SDK for iOS"
s.homepage = "http://dev.umeng.com/analytics/ios/quick-start"
s.author = { "DianQK" => "dianqk@icloud.com" }
s.platform = :ios, "8.0"
s.source = { :http => "http://dev.umeng.com/system/resources/W1siZiIsIjIwMTcvMDEvMjIvMTFfMDNfMjRfNzM0X3Vtc2RrX0lPU19hbmFseWljc19ub19pZGZhX3Y0LjIuNS56aXAiXV0/umsdk_IOS_analyics_no-idfa_v4.2.5.zip" }
s.vendored_frameworks = "*/UMMobClick.framework"
s.framework = "CoreTelephony"
s.libraries = "sqlite3", "z"
s.requires_arc = false
s.xcconfig = { "LIBRARY_SEARCH_PATHS" => "\"$(PODS_ROOT)/UMengAnalytics-NO-IDFA/**\"" }
s.prepare_command = <<-EOF
mkdir umsdk_IOS_analyics_no-idfa_v4.2.5/UMMobClick.framework/Modules
touch umsdk_IOS_analyics_no-idfa_v4.2.5/UMMobClick.framework/Modules/module.modulemap
cat <<-EOF > umsdk_IOS_analyics_no-idfa_v4.2.5/UMMobClick.framework/Modules/module.modulemap
framework module UMMobClick {
header "MobClick.h"
header "MobClickGameAnalytics.h"
header "MobClickSocialAnalytics.h"
export *
link "z"
link "sqlite3"
}
\EOF
EOF
end

static_framework

Podfile 文件中使用 use_frameworks! 时,对于该 pod 使用静态库形式集成

语法 结果
#use_frameworks! .a
use_frameworks! .framework (static)
use_frameworks!, :linkage => :static .framework (static)

deprecated、deprecated_in_favor_of

将 pod 库标记为废弃。

social_media_url、description、screenshots、documentation_url

字面意思,不多赘述。

Platform

platform

支持的平台, 未指定表示支持所有平台。当需要支持多平台并添加设置时应当使用 deployment_target

1
2
3
spec.platform = :osx, '10.8'
spec.platform = :ios
spec.platform = :osx

deployment_target [M]

表示在某个平台上的最小部署版本。

1
2
spec.ios.deployment_target = '13.0'
spec.osx.deployment_target = '10.8'

deployment_target 是对 platform 的扩充

Build settings

dependency [M]

依赖其他的 pod库 或者本 pod 库的其他 subpsec。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
s.dependency 'AFNetworking', '~> 1.0'
s.dependency 'AFNetworking', '~> 1.0', :configurations => ['Debug']
s.dependency 'AFNetworking', '~> 1.0', :configurations => :debug
s.dependency 'RestKit/CoreData', '~> 0.20.0'
s.ios.dependency 'MBProgressHUD', '~> 0.5'
------
s.subspec "Core" do |ss|
ss.source_files = "Sources/Moya/", "Sources/Moya/Plugins/"
ss.dependency "Alamofire", "~> 5.0"
ss.framework = "Foundation"
end

s.subspec "RxSwift" do |ss|
ss.source_files = "Sources/RxMoya/"
ss.dependency "Moya/Core"
ss.dependency "RxSwift", "~> 5.0"
end

对于版本号的要求,推荐使用较于宽松的 ~>,严格的版本要求会降低与其他 pod 库的兼容性。

info_plist [M]

添加到生成的 Info.plist 文件中的键值对。键值对将会与 cocoapods 生成的键值对进行 合并, 重复时将会覆盖已有键值对。

对于 library spec,对于静态库不起作用,对于动态库将合并到生成的 Info.plist 中。

不支持 subspec(app spec 和 test spec 除外)

对于 app_spec :键值对将合并到 App 的 Info.plist 中。
对于 test_spec (1.3.0 引入):键值对将合并到 test Bundle 的 Info.plist 中。

app_sepc 在 1.7.0 版本引入, 如果想要了解某个 Pod 怎么使用,我们需要去单独编译它的 Example 工程。 现在 Pod 内置了 app_spec DSL。

1
2
3
4
5
6
Pod::Spec.new do |s|
....
s.app_spec 'SampleApp' do |app_spec|
app_spec.source_files = 'Sample/*.swift'
end
end

如果想要集成 SampleApp 进来,只需如下这般

1
2
3
4
target 'MyApp' do
use_frameworks!
pod 'CoconutLib', '~> 1.0', :appspecs => ['SampleApp']
end

test_spec 1.3.0 引入

requires_arc [M]

指定使用 ARC 的文件,参数是 文件或文件列表或 true,true 表示所有文件都是用 ARC。

不使用 ARC 的文件,都会被添加 fno-objc-arc 编译标志。

默认参数是 true

1
2
3
4
spec.requires_arc = true
spec.requires_arc = false
spec.requires_arc = 'Classes/Arc'
spec.requires_arc = ['Classes/*ARC.m', 'Classes/ARC.mm']

frameworks [M]

集成本 pod 库的 target 需要链接的系统 framework 列表。

[M]
1
2
spec.ios.framework = 'CFNetwork'
spec.frameworks = 'QuartzCore', 'CoreData'

weak_frameworks [M]

集成本 pod 库的 target 需要 链接的系统 framework 列表。

1
2
spec.weak_framework = 'Twitter'
spec.weak_frameworks = 'Twitter', 'SafariServices'

弱链接表示可以在没有该特定框架可用的设备/ OS上运行,仅应用于真正不需要的框架,并且在正确编写代码以适应缺少这些框架的情况下使用。

例如 UserNotifications framework 是一个 iOS 10 才有的 framework,通过 weak linking + runtime availability check,可以在低于 iOS 10 的平台上安全地跑起来

libraries [M]

集成本 pod 库的 target 需要链接的系统 libray 列表。

1
2
spec.ios.library = 'xml2'
spec.libraries = 'z'

compiler_flags [M]

传递到编译器的编译标志列表

1
2
spec.compiler_flags = '-DOS_OBJECT_USE_OBJC=0', '-Wno-format'
spsc.ios.compiler_flags = 'xx', 'yy'

pod_target_xcconfig [M]

修改 pod 库的编译选项

1
2
s.ios.pod_target_xcconfig = { 'PRODUCT_BUNDLE_IDENTIFIER' => 'com.alamofire.AFNetworking' }
s.pod_target_xcconfig = { 'GCC_WARN_ABOUT_RETURN_TYPE' => 'YES_ERROR'}

user_target_xcconfig [M]

修改依赖本 pod 库的项目的编译设置。不推荐使用,Pod 库不应该污染用户的编译设置,可能会引起冲突。

应该使用 pod_target_xcconfig 应用相同的标志位,只影响自己 pod 库如何编译。

prefix_header_contents [M]

注入到 pods 工程 target 下 prefix.pch 文件中的内容。不推荐使用。

1
2
spec.ios.prefix_header_contents = '#import <UIKit/UIKit.h>'
spec.prefix_header_contents = '#import <UIKit/UIKit.h>', '#import <Foundation/Foundation.h>'

prefix_header_file [M]

注入到 pods 工程 target 下 prefix.pch 文件中的文件。不推荐使用。默认为 true。

1
2
spec.ios.prefix_header_file = 'iphone/include/prefix.pch'
spec.prefix_header_file = false

module_name

替换 framework 或 clang module 的默认的模块名。

1
2
3
4
Pod::Spec.new do |s|
s.name = 'Book'
...
s.module_name = 'Books'
1
2
import Book // error
import Books

header_dir、header_mappings_dir [M]

。。。

script_phases [M]

添加一个 build phase 到 pod 库的 build phases 中,每次编译都会被执行。与 prepare_command 不同,可以使用编译器所有的环境变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
spec.ios.script_phase = { :name => 'Hello World', :script => 'echo "Hello World"' }
------
spec.script_phase = { :name => 'Hello World', :script => 'echo "Hello World"', :execution_position => :before_compile }
------
spec.script_phase = { :name => 'Hello World', :script => 'puts "Hello World"', :shell_path => '/usr/bin/ruby' }
------
spec.script_phase = { :name => 'Hello World', :script => 'echo "Hello World"',
:input_files => ['/path/to/input_file.txt'], :output_files => ['/path/to/output_file.txt']
}
------
spec.script_phase = { :name => 'Hello World', :script => 'echo "Hello World"',
:input_file_lists => ['/path/to/input_files.xcfilelist'], :output_file_lists => ['/path/to/output_files.xcfilelist']
}
------
spec.script_phases = [
{ :name => 'Hello World', :script => 'echo "Hello World"' },
{ :name => 'Hello Ruby World', :script => 'puts "Hello World"', :shell_path => '/usr/bin/ruby' },
]

pod 库可以添加多个 phase,按照在 podspec 文件中的声明顺序添加。

如果 pod 库包含了 build phase,则会在 pod install 的时候向用户发出警告。

File patterns

source_files [M]

pod 库的源文件

1
2
3
spec.source_files = 'Classes/**/*.{h,m}'
-----
spec.ios.source_files = 'Class/ios/**/*.{h,m}'

public_header_files [M]

用作公共头的文件模式列表。

1
2
spec.public_header_files = 'Headers/Public/*.h'
spec.ios.public_header_files = 'xx/xx/*.h'

如果不指定 public_header_files, 那么 source_files 中所有的 header 都被视为公开的头文件。public 的 header 将会出现在编译产物的 Headers 文件夹中。

private_header_files [M]

不希望对外暴露的头文件。字面上与 public_header_files 相反。但实际上如果想对外隐藏某些头文件,那么这些字段不能出现在 private_header_filespublic_header_files 字段中,才能真正的实现隐藏头文件。否则 OC 依然可以通过 #import <xxx/xxx.h> 的形式导入改头文件。

private 的 header 将会出现在产物的 PrivateHeaders 文件夹中。framework search path 在 framework 内搜索头文件时,先在 Headers 文件夹中查找,如果找不到则会在 PrivateHeaders 文件夹中查找。这也是为什么 private_header_files 不能将 header 真正隐藏的原因。

关于 build phases 的 Headers 中 public、private、project 三者的区别可以查看 Xcode Help - What are build phases? 的解释。

Associates public, private, or project header files with the target. Public and private headers define API intended for use by other clients, and are copied into a product for installation. For example, public and private headers in a framework target are copied into Headers and PrivateHeaders subfolders within a product. Project headers define API used and built by a target, but not copied into a product. This phase can be used once per target.
Public 和 Private 是指可以供外界使用的头文件,且分别放在最终产物的 Headers 和 PrivateHeaders 目录中,而 Project 中的头文件是不对外使用的,也不会放在最终的产物中。

StackOverflow - Xcode: Copy Headers: Public vs. Private vs. Project?
StackOverflow - Understanding Xcode’s Copy Headers phase 里面还记载了三者的区别

Public: The interface is finalized and meant to be used by your product’s clients. A public header is included in the product as readable source code without restriction. Private: The interface isn’t intended for your clients or it’s in early stages of development. A private header is included in the product, but it’s marked “private”. Thus the symbols are visible to all clients, but clients should understand that they’re not supposed to use them. Project: The interface is for use only by implementation files in the current project. A project header is not included in the target, except in object code. The symbols are not visible to clients at all, only to you.

Public 还是通常意义上的 Public,Private 则代表 In Progress 的含义,至于 Project 才是通常意义上的 Private 含义。

从预编译的角度理解Swift与Objective-C及混编机制

vendored_frameworks [M]

封装 动/静态库, 与此 pod 一同发布。
具体参见 framework 与 vendored_framework

1
2
spec.ios.vendored_frameworks = 'Frameworks/MyFramework.framework'
spec.vendored_frameworks = 'MyFramework.framework', 'TheirFramework.framework'

vendored_libraries [M]

封装 静态库 .a, 与此 pod 一同发布

1
2
spec.ios.vendored_library = 'Libraries/libProj4.a'
spec.vendored_libraries = 'libProj4.a', 'libJavaScriptCore.a'

resource_bundles [M]

为 pod 库创建资源 bundle,接受一个 hash , key 是要创建的 bundle 的名字,value 是由于文件匹配的字符串。

对于静态库强烈推荐使用 resource_bundles 而不是 resources, 以避免命名冲突。bundle 的名称最少应该包含 pod 库的名字以减少命名冲突。

1
2
3
4
5
spec.ios.resource_bundle = { 'MapBox' => 'MapView/Map/Resources/*.png' }
spec.resource_bundles = {
'MapBox' => ['MapView/Map/Resources/*.png'],
'MapBoxOtherResources' => ['MapView/Map/OtherResources/*.png']
}

resources [M]

将资源文件直接 copy 到目标 target 目录下

type resource_bundles resources
动态库 /Frameworks/podname.framework/xx.bundle/file.txt /Frameworks/podname.framework/file.txt
静态库 /xx.bundle/file.txt /file.txt

resource_bundle 会自己创建 bundle 用于存放资源文件,而 resources则不会,而是直接 copy。

exclude_files [M]

在其他文件匹配模式中排除的文件

1
2
spec.ios.exclude_files = 'Classes/osx'
spec.exclude_files = 'Classes/**/unused.{h,m}'

参考

https://github.com/DeclarativeHub/Bond/blob/master/Bond.podspec

preserve_paths [M]

需要保留的文件,Cocoapods 在下载完原文件后会删除没有被匹配到的文件。使用这个指令可以使其保留。

1
2
spec.preserve_path = 'IMPORTANT.txt'
spec.preserve_paths = 'Frameworks/*.framework'

module_map [M]

将 pod 集成为 framework 时应使用的 module map 文件。通常情况下 CocoaPods 会通过 public headers 自动创建 module map。当自动创建的 module map 不能满足需求时,才手动指定

1
spec.module_map = 'source/module.modulemap'

参考
https://github.com/yapstudios/YapDatabase/blob/master/YapDatabase.podspec

Subspecs

subspec

表示一个库的子模块的规范。subspec 实行“双重等级制度”,一方面 spec 自动继承其所有 subspec 作为依赖项(除非指定了默认的 spec),另一方面,subspec 继承了父级的属性值,所以在在父级 spec 中定义通用的属性值。

1
2
3
4
pod 'ShareKit', '2.0' # 安装全部
----
pod 'ShareKit/Twitter', '2.0' # 只安装 Twitter
pod 'ShareKit/Pinboard', '2.0' # 只安装 PinBoard

只安装某个子模块时 cocoapods 自动处理定义在 Root spec 中的源文件、依赖及其他需要的 attribute,并处理重复定义引起的问题。

不同的 subspec 拥有不同的 source files

1
2
3
4
5
6
7
subspec 'Twitter' do |sp|
sp.source_files = 'Classes/Twitter'
end

subspec 'Pinboard' do |sp|
sp.source_files = 'Classes/Pinboard'
end

subspec 依赖其他 subspec

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
s.subspec "Core" do |ss|
ss.source_files = "Sources/Moya/", "Sources/Moya/Plugins/"
ss.dependency "Alamofire", "~> 5.0"
ss.framework = "Foundation"
end

s.subspec "ReactiveSwift" do |ss|
ss.source_files = "Sources/ReactiveMoya/"
ss.dependency "Moya/Core"
ss.dependency "ReactiveSwift", "~> 6.0"
end

s.subspec "RxSwift" do |ss|
ss.source_files = "Sources/RxMoya/"
ss.dependency "Moya/Core"
ss.dependency "RxSwift", "~> 5.0"
end

subspec 嵌套

1
2
3
4
5
6
7
8
Pod::Spec.new do |s|
s.name = 'Root'

s.subspec 'Level_1' do |sp|
sp.subspec 'Level_2' do |ssp|
end
end
end

test_spec、app_spec、requires_app_host [M]、app_host_name [M]

  • test_spec 1.3.0 引入,将测试用例与 pod 一起发布
  • app_spec 1.7.0 引入,将演示程序与 pod 一起发布
  • requires_app_host [M]、app_host_name [M]: test_spec 是否需要一个 App 宿主、App 宿主的名字(可以指向 app_spec 的名字)

参考
https://github.com/square/workflow-swift/blob/main/Development.podspec
https://github.com/firebase/firebase-ios-sdk/blob/master/FirebaseMessaging.podspec
https://github.com/gorastudio/SCNRecorder/blob/master/SCNRecorder.podspec

scheme [M]

Pod 库的作者现在可以为他们的 specs,test specs 以及 app specs 自定义 scheme 的生成。目前支持指定环境变量和启动参数,并可以在将来轻松扩展。

1
2
3
4
5
6
7
8
9
10
11
12
Pod::Spec.new do |spec|
spec.name = 'CoconutLib'
spec.version = '1.0'
# ... rest of root spec entries go here
spec.test_spec 'Tests' do |test_spec|
test_spec.source_files = 'Tests/**/*.swift'
test_spec.scheme = {
:launch_arguments => ['Arg1', 'Arg2'],
:environment_variables => { 'Key1' => 'Val1'}
}
end
end

default_subspecs [M]

用做默认依赖的 subspec 的名字数组,如果不指定则将全部的 subspec 作为依赖。还可以指定为 :none,将所有 subspec 置为可选。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# moya.podspec
s.default_subspec = "Core"
s.subspec "Core" do |ss|
ss.source_files = "Sources/Moya/", "Sources/Moya/Plugins/"
ss.dependency "Alamofire", "~> 5.0"
ss.framework = "Foundation"
end

s.subspec "ReactiveSwift" do |ss|
ss.source_files = "Sources/ReactiveMoya/"
ss.dependency "Moya/Core"
ss.dependency "ReactiveSwift", "~> 6.0"
end

s.subspec "RxSwift" do |ss|
ss.source_files = "Sources/RxMoya/"
ss.dependency "Moya/Core"
ss.dependency "RxSwift", "~> 5.0"
end

Multi-Platform support

ios、 osx/macos、 tvos、 watchos

1
2
3
4
5
spec.ios.source_files = 'Classes/ios/**/*.{h,m}'
spec.osx.source_files = 'Classes/osx/**/*.{h,m}'
spec.macos.source_files = 'Classes/macos/**/*.{h,m}'
spec.tvos.source_files = 'Classes/tvos/**/*.{h,m}'
spec.watchos.source_files = 'Classes/watchos/**/*.{h,m}'