[toc]
本文来自拉勾网课程整理。
前言
要成为一个优秀的iOS开发者,我们要做的事情远多于“开发”,例如我们要构建和打包 App,管理证书,为 App 进行签名,把 App分发给测试组,上传 App 到App Store,等等。这些操作不但繁琐费时,而且容易出错。那么,有没有更便利的方法呢?有,那就是使用 fastlane 来完成这些重复性的工作。接下来这一讲,我们主要聊的也就是这个主题。
fastlane 安装
fastlane 是用 Ruby 语言编写的一个命令行工具,可以自动化几乎所有iOS开发所需要的操作,例如自动打包和签名App,自动上传到 App Store 等等。有了 fastlane,我们就可以开发一套统一的、可靠的和可共享的配置,团队所有成员都可以通过这套配置实现自动化操作,减少重复性劳动。
如何安装 fastlane 呢?我记得在第一讲就曾提到过,可以使用 Bundler 来安装,只需要在 Gemfile 文件里面添加以下一行即可:
gem "fastlane", "2.166.0"
注意,由于是通过Bundler来安装fastlane,每次执行fastlane命令前,都需要加上bundle exec(bundle exec fastlane)。不过为了简洁,在这里后面凡涉及 fastlane 命令时,我会省略bundle exec部分。
Action 与 Lane
fastlane 为我们提供了一百多个 Action,它们是 iOS 项目开发中所有自动化操作的基础。所谓的Action,你可以理解成是 fastlane 自动化流程中的最小执行单元。一般常用的Action有:
-
scan,用于自动测试App; -
cert,用于自动生成和管理iOS App签名的证书; -
sigh,用于自动生成、更新、下载和修复Provisioning Profile; -
match,为整个团队自动管理和同步证书和Provisioning Profile; -
gym,用于自动构建和打包App; -
snapshot,用于自动在不同设备上截屏; -
pilot,用于自动把App部署到TestFlight并管理测试用户; -
deliver,用于自动把App上传到App Store; -
pem,用于自动生成和更新推送消息的Profile。
这些Action怎么执行呢?我们可以通过fastlane <action>(例如fastlane scan)来执行。下面是执行效果,它提示我选择其中一个 Scheme 来继续执行。

从运行情况可知,尽管这些 Action 为我们提供了不少便利,但还是需要手工输入来继续。所以,我不推荐你直接使用这些 Action,而是根据项目需要,在开发自己的自动化操作时通过传入合适的参数来调用 fastlane所提供的Action。
具体来说,我们可以把所需的 Action 组合在一起,开发出对应的自动化操作。在 fastlane 中,我们把这个自动化操作或者任务叫作Lane。实际上, iOS 开发中的所有自动化操作,主要通过Lane来封装和配置的。

Lane 和 Action 的关系如上图所示, 一条 Lane 可以通过参数调用一个或几个 Action 。以 Moments app 为例,我们要自动打包和签名 App,那么我就建了一条名叫archive_appstore的 Lane。因为这条Lane用到的“更新签名”和“打包”在 fastalne 里已经提供了相关的 Action——update_code_signing_settings和gym,我就可以到它官网去寻找,从而减轻了开发工作量。
一般,iOS 项目所需的自动化操作都配置为 Lane 并保存在 Fastfile文件,由 Git 统一管理起来,共享给所有成员。然后,大家就可以使用统一的自动化配置了。
这里的Fastfile文件是怎么出来的呢?
它是由fastlane init命令自动生成。这条命令会建立一个 fastlane 文件夹,文件夹里除了 Fastfile,还有 Appfile,以及执行过程中所生成的一些中间文件(如截图、日志与报告等)。因为我们之前已经在 .gitignore 文件里把这些中间文件忽略了,因此这些中间文件不再保存到 Git 里面。
fastlane 文件夹里的Appfile,用于保存 App 的唯一标识符和Apple ID等信息。当 fastlane 在执行一个Action的时候,首先会使用传递进来的参数,当参数没有传递进来时,fastlane会从 Appfile 文件中查找并使用对应的信息。
比如,我们在 Appfile 配置了app_identifier "com.ibanimatable.moments"以后,在调用matchAction 时可以不传入app_identifier参数,fastlane 会自动把"com.ibanimatable.moments"作为app_identifier的值进行调用。
但是为了方便管理所有的Lane,保证每次执行的效果都一样,我建议在每次调用Action的时候,都明确传递每一个所需的参数,而不是从 Appfile 文件读取。下面我就演示下如何明确传递每一个参数来执行matchAction。
match(
type: "appstore",
force: true,
storage_mode: "git",
git_url: "https://github.com/JakeLin/moments-codesign",
app_identifier: "com.ibanimatable.moments", # pass app_identifier explicitly
team_id: "6HLFCRTYQU",
api_key: api_key
)
常用 Lane 定义
通过上面的介绍你已经知道,我们会使用Lane来封装项目所需的各个自动化操作。那么,这些Lane是如何开发定义的呢?接下来,我就为你介绍几种非常实用的Lane,一起来看看怎么做。
扫描和检查代码
每条Lane的定义都是放在一个lane <lane_name> do <lane_body> end的代码块里面。它以关键字lane开头,接着是这条 Lane 的名字。 下面是用于检查代码的 Lane 源码。
lane :lint_code do
puts("Lint code using SwfitLint")
swiftlint(
mode: :lint,
executable: "./Pods/SwiftLint/swiftlint", # Important if you've installed it via CocoaPods
config_file: './Moments/.swiftlint.yml',
raise_if_swiftlint_error: true)
end
在上面的例子中,我们定义了一个叫作lint_code的 Lane。因为 fastlane 使用 Ruby 开发,所以在 Fastfile 里面,Lane 的名字也遵循它的编码规范,使用小写字母和下划线组合的蛇式命名法。
Lane 的实现逻辑放在do和end中间,我们可以调用fastlane提供的任意 Action。在这个例子中我们就调用了swiftlintAction,并把lint传递给mode参数,以此来执行代码扫描和检查操作。
特别需要注意的是,由于我们之前使用了CocoaPods 来安装 SwiftLint,因此要为executable参数指定 SwiftLint 的安装路径./Pods/SwiftLint/swiftlint。同时要把 .swiftlint.yml 文件的所在路径也传递给config_file参数。这样就能保证 fastlane 使用了统一的 SwiftLint 版本和规则文件,方便团队所有人执行该Lane时得到统一的效果。
当一条 Lane 开发配置完毕以后,我们就可以在项目的根目录执行 fastlane <lane_name>。比如扫描和检查代码的 Lane ,我们可以在终端输入fastlane lint_code看到它的执行效果。
Driving the lane 'ios lint_code'
Lint code using SwfitLint
--- Step: swiftlint ---
$ ./Pods/SwiftLint/swiftlint lint --config ./Moments/.swiftlint.yml
Linting Swift files in current working directory
Linting 'Strings.swift' (1/87)
Linting 'MomentListItemViewModel.swift' (2/87)
Linting ......s
UIButtonExtensions.swift:14:46: warning: no_hardcoded_strings Violation: Please do not hardcode strings and add them to the appropriate Localizable.strings file; a build script compiles all strings into strongly typed resources available through Generated/Strings.swift, e.g. `L10n.accessCamera (no_hardcoded_strings)
Done linting! Found 6 violations, 0 serious in 87 files.
fastlane.tools finished successfully
在执行过程中,fastlane 先从 Fastfile 文件里名叫lint_code的Lane的定义,然后执行了该 Lane里使用到的swiftlint Action。swiftlint Action会把项目下87 个 Swift 源代码文件进行扫描和检查,并把所有不符合规范的代码提示给我们。
格式化代码
检查代码之后,接下来就是清理不符合规范的代码,比如删掉所有代码中不必要的空格或者空行,修正缩进的大小等等。我们可以定义一条叫作format_code的 Lane 来执行该功能。有了它以后,我们只需要执行fastlane format_code就能把整个项目所有的代码进行格式化。
lane :format_code do
puts("Lint and format code using SwfitLint")
swiftlint(
mode: :autocorrect,
executable: "./Pods/SwiftLint/swiftlint", # Important if you've installed it via CocoaPods
config_file: './Moments/.swiftlint.yml',
raise_if_swiftlint_error: true)
end
format_code和lint_code两条 Lane 都使用了swiftlintAction,唯一不同的地方是为mode参数传递了autocorrect。
排序 Xcode 项目文件列表
在多人开发的项目下,我们经常会修改项目文件,这往往很容易引起合并冲突,而合并xcodeproj文件又是一件非常麻烦的事情。怎么办呢?
一个有效办法就是在每次新建源代码和资源文件时,把xcodeproj里面的文件列表进行重新排序。这样能极大地减低合并冲突的发生。我们把这一个经常使用到的操作也配置到 Fastfile里面,如下所示。
lane :sort_files do
puts("Sort the files for the Xcode project")
sh "../scripts/sort-Xcode-project-file.pl ../Moments/Moments.xcodeproj"
end
可以看到,fastlane 除了能调用其提供的Action以外,还可以通过sh来调用其他程序命令。在这里我们调用了由苹果公司提供的一个Perl 程序来为 xcodeproj 里面的文件列表进行排序。你也可以在代码仓库找到这个Perl 程序。
调用其他 Lane 操作
除了调用一些程序命令(如sh)以外,一条 Lane 还可以调用 Fastfile 里面其他的 Lane。例如我们定义了一条叫作prepare_pr的Lane,它可以帮我们在提交 Pull Request 之前做一些必要的准备。下面这个代码表示的就是,这条 Lane 在内部调用了另外两条 Lane ——format_code和sort_files,以此来同时完成格式化代码和排序 Xcode项目文件列表的操作。
lane :prepare_pr do
format_code
sort_files
end
定义私有 Lane 和返回值
类似于 Swift 语言能通过private来定义内部使用的方法,我们也能定义私有 Lane 给Fastfile 内的其他Lane所调用,提高代码的复用。其做法就是把原先的关键字lane替换成private_lane。例如我们定义一条叫作get_pi的私有 Lane,代码如下。
private_lane :get_pi do
pi = 3.1415
pi
end
该 Lane 的实现体有两行代码,第一行是给一个临时变量pi赋值。第二行表示把pi作为返回值传递给调用者。例如下面就演示了如何调用get_pi并取得返回值。
lane :foo do
pi = get_pi
puts(pi)
end
这是执行fastlane foo的结果:
Driving the lane 'ios foo'
--- Step: Switch to ios get_pi lane ---
Cruising over to lane 'ios get_pi'
Cruising back to lane 'ios foo'
3.1415
fastlane 首先调用fooLane,然后进去get_piLane 并返回到foo,同时把返回结果打印出来。
总结
这一讲我介绍了如何从头开始搭建一个 fastlane 环境。在这里需要注意三点:
- 不要单独手工执行
fastlane所提供的Action,而是使用Fastfile文件来统一开发、配置和管理日常中经常使用的所有自动化操作; - 在开发我们的
Lane时,要优先使用和调用fastlane提供的Action,因为这些Action都是经过社区完善的,且会随着Xcode版本的升级而更新; - 当我们调用
fastlane所提供的Action时,要明确传递各个参数,在执行过程中就无须任何手工交互就能从头到尾执行整个操作。

有了项目需要的所有Lane以后,能有效减轻团队成员的重复劳动,并为项目的自动化和工程化打下坚实的基础。
源码地址:
Fastfile 文件地址: