与公司 QA 聊天,已不止一次被吐槽说移动端从开发环境转到生产环境时,还要靠修改代码来配置对应的环境参数。她认为,从 App 转测试之后,就不应该再修改代码,可以把所有的环境配置都整合到配置文件中,这样打不同环境下的安装包时,会自动选择对应的环境参数。这里说到的环境参数包括但不仅限于: webservice 地址,友盟 AppKey,极光推送 AppKey 和是否是生产环境标志等。

其实,我也讨厌修改环境参数啊,😂

为达成上述目的,主要是使用 Xcode 的 Configurations Setting File(即后缀为 xcconfig 文件) 来配置开发不同阶段下的环境。本文包含的内容如下:

  1. Xcode Target
  2. Xcode Project
  3. Build Setting的继承关系
  4. 如何使用xcconfig文件来配置不同开发阶段的环境

包含了一些与 build settings 相关的知识。

Xcode Target

target, 官方文档如下解释:

A target specifies a product to build and contains the instructions for building the product from a set of files in a project or workspace. A target defines a single product; it organizes the inputs into the build system—the source files and instructions for processing those source files—required to build that product. Projects can contain one or more targets, each of which produces one product.

target 定义了生成的唯一 product, 它将构建该 product 所需的文件和处理这些文件所需的指令集整合进 build system 中。Projects 会包含一个或者多个 targets,每一个 target 将会产出一个 product.

The instructions for building a product take the form of build settings and build phases, which you can examine and edit in the Xcode project editor. A target inherits the project build settings, but you can override any of the project settings by specifying different settings at the target level. There can be only one active target at a time; the Xcode scheme specifies the active target.

这些指令以 build setting 和 build phases 的形式存在,你可在 Xcode 的项目编辑器(TARGETS->Build Setting, TARGETS->Build Phases)中进行查看和编辑。target 中的 build setting 参数继承自 project 的 build settings, 但是你可以在 target 中修改任意 settings 来重写 project settings,这样,最终生效的 settings 参数以在 target 中设置的为准. Project 可包含多个 target, 但是在同一时刻,只会有一个 target 生效,可用 Xcode 的 scheme 来指定是哪一个 target 生效.

A target and the product it creates can be related to another target. If a target requires the output of another target in order to build, the first target is said to depend upon the second. If both targets are in the same workspace, Xcode can discover the dependency, in which case it builds the products in the required order. Such a relationship is referred to as an implicit dependency. You can also specify explicit target dependencies in your build settings, and you can specify that two targets that Xcode might expect to have an implicit dependency are actually not dependent. For example, you might build both a library and an application that links against that library in the same workspace. Xcode can discover this relationship and automatically build the library first. However, if you actually want to link against a version of the library other than the one built in the workspace, you can create an explicit dependency in your build settings, which overrides this implicit dependency.

target 和其生成的 product 可与另一个 target 有关,如果一个 target 的 build 依赖于另一个 target 的输出,那么我们就说前一个 target 依赖于后一个 target .如果这些 target 在同一个 workspace 中,那么 Xcode 能够发现这种依赖关系,从而使其以我们期望的顺序生成 products.这种关系被称为隐式依赖关系。同时,你可以显示指定 targets 之间的依赖关系,并且这种依赖关系会覆盖 Xcode 推测出的隐式依赖关系。

指定 targets 之间的依赖关系的地方在 Project Editor->TRAGETS->Build Phases->Target Dependencies 处设置。

Xcode Project

官方文档的解释如下:

An Xcode project is a repository for all the files, resources, and information required to build one or more software products. A project contains all the elements used to build your products and maintains the relationships between those elements. It contains one or more targets, which specify how to build products. A project defines default build settings for all the targets in the project (each target can also specify its own build settings, which override the project build settings).

Xcode project 是一个仓库,该仓库包含了所有的文件,资源和用于生成一个或者多个 software products 的信息。它包含一个或者多个 targets,其中的每一个 target 指明了如何生成 products。project 为其拥有的所有 targets 定义了默认的 build settings,当然,每一个 target 能够制定其自己的 build settings,且 target 的 build settings 会重写 project 的 build settings。

Xcode project 文件包含以下信息:

  • 源文件的引用:
    • 源码,包括头文件和实现文件
    • 内部和外部的库或者框架
    • 资源文件
    • 图片文件
    • Interface Builder(nib)文件
  • 文件结构导航中用来组织源文件的组
  • Project-level build configurations.你可以为 project 指定多个 build configuration,例如,project 中默认包含 debug 和 release 两种 build settings.
  • Targets, 每一个 target 指定了:
    • project 生成的 product
    • 生成 product 所需的源文件
    • 生成 product 所需的配置文件,包括对其他 targets 的依赖以及一些其他设置;当 targets 的 build configurations 没有重写 project-level 的 build settings 时,会直接使用 project-level 的 build setting.
  • 可执行环境,该环境用于调试或者测试程序,每个可执行环境会指定:
    • 运行或者调试程序时加载的可执行程序
    • 传递给可执行程序的命令行参数
    • 运行程序时需设置的环境变量

project 可独立存在,也可被包含在 workspace 中。

Build Setting 的继承关系

官方文档内容如下:

A build setting is a variable that contains information about how a particular aspect of a product’s build process should be performed. For example, the information in a build setting can specify which options Xcode passes to the compiler.

You can specify build settings at the project or target level. Each project-level build setting applies to all targets in the project unless explicitly overridden by the build settings for a specific target.

build setting 中包含了 product 生成过程中所需的参数信息。你可以在 project-level 和 target-level 层指定 build settings。project-level 的 build settings 适用于 project 中的所有targets,但是当 target-level 的 build settings 重写了 project-level 的 build settings,以 target-level 中的 build settings 中的值为准。

Each target organizes the source files needed to build one product. A build configuration specifies a set of build settings used to build a target's product in a particular way. For example, it is common to have separate build configurations for debug and release builds of a product.

一个 build configaration 指定了一套 build settings 用于生成某一 target 的 product,例如,在 Xcode 创建项目时默认就有两套独立的 build configarations, 分别用于生成 debug 和 release 模式下的 product。

In addition to the default build settings provided by Xcode when you create a new project from a project template, you can create user-defined build settings for your project or for a particular target. You can also specify conditional build settings. The value of a conditional build setting depends on whether one or more prerequisites are met. This mechanism allows you to, for example, specify the SDK to use to build a product based on the targeted architecture.

除了创建工程时生成的默认 build settings,你也可以自定义 project-level 或者 target-level 的 build settings.

关于继承关系,The Unofficial Guide to xcconfig files 这里也有详细的说明,强烈建议阅读。

现在就来看看如何使用自定义的 build settings 来达到本文开始处提到的需求.

如何使用 xcconfig 文件来配置不同开发阶段的环境

目前公司中的开发大致分两个阶段,第一阶段:开发阶段,此时所打包都是使用 development 的证书,极光和友盟统计的账号都是使用开发者自己申请的账号,webservice 的地址使用开发环境地址;第二阶段:uat 阶段,此时属于预发版阶段,此时打包使用 ad-hoc 的证书,极光和友盟统计的账号使用公司申请生成账号,webservice 使用的特定的预发版环境;另外,打上传到 App Store 的生产包,使用 distribution 的证书,webservice 的地址使用生产环境的地址。

由此,可新建一种 build configuration, 由 Xcode 自动生成的 Release 复制而来,如下所示:

新建Configurations新建Configurations

并命名为 PreRelease。

官方文档Adding a Build Configuration 中如下提到:

A configuration file is a plain text file with a list of build setting definitions, one per line. You can base a build configuration only on a configuration file that is in your project, not on an external file.

When you base a target or project’s build configuration on a configuration file, that build configuration automatically inherits the build setting definitions in that configuration file (and any configuration files it includes). If you then modify the value of any of those build settings in the target or project, the new value is used instead of the value in the configuration file.

Build settings defined at the target level override any values assigned to those build settings at the project level. Therefore, target-level configurations take precedence over any project-level configurations.

这里需要注意的是:当你的 target-level 或者 project-levle 的 build configurations 基于配置文件时,build configuration 会自动继承配置文件(以及配置文件中引入的配置文件)中定义的 build settings,但是如果你又在之后 target 或者 project 中修改了配置文件中定义的 build settings 值,那么最终配置文件中的值会失效,实际使用的是 target 或者 project 中设置的值。

这里鉴于公司的情况,新建了 Debug.xcconfig/PreRelease.xcconfig/Release.xcconfig 配置对应于开发阶段、预发版阶段、上传 AppStore 三种情况下的打包。

新建一个 xcconfig 目录,在该目录下新建配置文件:

创建配置文件创建配置文件

根据项目情况,每个配置文件中都包含同样的 key 值,内容大致如下:

1
2
3
4
5
6
7
8
9
10
11
//网络请求baseurl
WEBSERVICE_URL = @"http:\/\/127.0.0.1"

//友盟配置
UMENG_APPKEY = @"xxxvvv555999=="

//极光推送配置
JPUSH_DEVELOPMENT_APPKEY = @"nnncccvvvwww"
IS_PRODUCATION = NO

#include "Generator.xcconfig"

你可在配置文件中包含其他配置文件,其中 Generator.xcconfig 文件的内容是:

1
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) WEBSERVICE_URL='$(WEBSERVICE_URL)' MESSAGE_SYSTEM_URL='$(MESSAGE_SYSTEM_URL)' UMENG_APPKEY='$(UMENG_APPKEY)'  IS_PRODUCATION='$(IS_PRODUCATION)'

其作用是将配置文件中定义的常量定义成预编译宏,以便于在代码中获取。

其中 GCC_PREPROCESSOR_DEFINITIONS, 文档如下:

Space-separated list of option specifications. Specifies preprocessor macros in the form foo (for a simple #define) or foo=1 (for a value definition). This list is passed to the compiler through the gcc -D option when compiling precompiled headers and implementation files.

GCC_PREPROCESSOR_DEFINITIONS 是 GCC 预编译头参数,通常我们可以在 Project 文件下的 Build Settings 对预编译宏定义进行默认赋值。在 Xcode7 下的路径为 Build Settings->Apple LLVM 7.x Preprocessing->Preprocessor Macros,

GCC_PREPROCESSOR_DEFINITIONSGCC_PREPROCESSOR_DEFINITIONS

想必大家看这个宏的名字已经知道它的作用了, 使用上和在 pch 头文件中添加宏定义没有太大的区别, 但有以下好处:

  1. Xcode 的 Project 的 Build Settings 是由一个 plist 文件进行描述的, plist 本质上是一个 XML 配置文件, 通过外部的脚本比较容易去修改。
  2. Preprocessor Macros 可以按照 Configuration 选项进行默认配置, 也就是说可以根据不同的环境预先制定不同定义的宏,或者为不同环境下的相同变量定义不同的值

xcconfig 支持可以根据不同的 Configuration 选项配置不同的文件。不同的 xcconfig 可以指定不同的 Build Settings 里的属性值, 这样子我们就可以通过项目 xcconfig 去修改 GCC_PREPROCESSOR_DEFINITIONS 的值了(最终目的就达到了)。

配置文件中变量定义好之后,怎么让 Xcode 自动加载呢?如下图设置所示,是将 project-level 的 build settings 基于配置文件,三种情况的 configurations 分别选择与之对应的配置文件。

将配置文件与项目关联将配置文件与项目关联

当我们想把 project-level 或者 target-level 中的 Build Settings 的设置挪动到 xcconfig 配置文件来设置时,是否需要一个个手动输入呢?当然不是,直接在 Build Settings 中选中你想要在 xcconfig 中配置的键值对所在行(当然也可以选多行),command + c复制,然后到对应的 xcconfig 中去粘贴就好了,记得在 Build Settings 中改为你想要的值后再复制,如果为默认值的话则只可复制其键。如果需要改回去的话,还是选中这行,command + delete 就恢复默认值了。

现在我们将设置挪动到了配置文件中,所有的配置文件都是键值对类型的文本文件,但是当同一个键同时存在于 target-level、project-level 和配置文件中时,到底是哪一个键值对起作用了呢?现在看看下图。

多个配置项列多个配置项列

注意: Xcode以从左至右的顺序设置解析的优先级,从左至右优先级降低,最左边的具有最高优先级,即 target-level > project-level > 自定义配置文件 > iOS 默认配置;且最左列 Resolved 列显示的是最终使用的值。那么如何使 Xcode 使用配置文件中的配置项呢?这需要选中要使用配置文件的行,点击 Delete 按键,你会发现项目的默认设置已经被删除,且 xcconfig 的配置文件列被标记为绿色。标记为绿色代表该列的值生效,其值应该与 Resolved 列的值相同。

最后,你可以像如下示例使用 xcconfig 中定义的宏:

1
NSLog(@"webservice url: %@, umeng appkey: %@", WEBSERVICE_URL, UMENG_APPKEY);

通过以上步骤,就达到了使用 xcconfig 文件来配置开发不同阶段时的环境变量的目的了。

文中内容为自己学习总结,如有错误之处请指正。如果觉得本文对你有帮助,就请用微信随意打赏我吧^_^


参考: