XCode的构建配置文件

Posted by CoderLeonidas on March 19, 2020

XCode的构建配置文件

软件开发最佳实践规定了配置与代码的严格分离。然而,苹果平台上的开发人员常常很难将这些指导原则与Xcode项目繁重的工作流程结合起来。

了解每个项目设置的功能以及它们之间的相互作用是一项需要多年磨练的技能。事实上,很多信息都深埋在Xcode的gui中,这对我们没有任何好处

导航到“project editor”的“Build Settings”选项卡,你会看到成百上千的生成设置,它们分布在项目、目标和配置的各个层上,更不用说其他六个选项卡了

幸运的是,有一种更好的方法来管理所有这些配置,而不需要在错综复杂的选项卡和显示箭头中点击。

接下来,我们将向您展示如何使用基于文本的xcconfig文件将构建设置从Xcode外部化,从而使您的项目更加紧凑、易于理解和强大。

查看XcodeBuildSettings.com,以获得最新版本的Xcode支持的每个构建设置的完整参考资料。

Xcode构建配置文件(通常由它们的xcconfig文件扩展名所知)允许在不使用Xcode的情况下声明和管理应用程序的构建设置。它们是纯文本,这意味着它们对源代码控制系统更友好,可以用任何编辑器进行修改。

基本上,每个配置文件都由一系列键值分配组成,其语法如下:

1
 BUILD_SETTING_NAME = value

例如,要指定项目的Swift语言版本,您应该像这样指定Swift版本构建设置:

1
 SWIFT_VERSION = 5.0

根据posix标准环境变量的名称仅由大写字母、数字和下划线(_)组成,我喜欢将这种约定称为尖叫蛇形大小写🐍🗯。

乍一看,xcconfig文件与.env文件有惊人的相似之处,它们使用简单的新行分隔语法。但是,Xcode构建配置文件的内容远不止这些。看哪

保留现有的值

要追加而不是替换现有定义,可以像这样使用$(inherited)变量

1
BUILD_SETTING_NAME = $(inherited)additional value

通常,您这样做是为了建立值列表,例如编译器在其中搜索frameworks以查找包含的头文件(FRAMEWORK_SEARCH_PATHS):

1
FRAMEWORK_SEARCH_PATHS = $(inherited) $(PROJECT_DIR)

Xcode按照以下顺序(从最低优先级到最高优先级)分配继承值

  • Platform Defaults
  • Xcode Project xcconfig File
  • Xcode Project File Build Settings
  • Target xcconfig File
  • Target Build Settings

空格用于分隔字符串和路径列表中的项。要指定包含空格的项,必须用引号(“)括起来。

引用值

可以使用以下语法通过声明名称替换其他设置中的值

1
BUILD_SETTING_NAME = $(ANOTHER_BUILD_SETTING_NAME)

替换可用于根据现有值定义新变量,或内联以动态构建新值。

1
2
OBJROOT = $(SYMROOT)
CONFIGURATION_BUILD_DIR = $(BUILD_DIR)/$(CONFIGURATION)-$(PLATFORM_NAME)

为引用的构建设置设置回退值

在Xcode 11.4及以后版本中,如果引用的构建设置计算结果为空,您可以使用默认的求值操作符来指定要使用的回退值。

1
$(BUILD_SETTING_NAME:default=value)

条件化构建设置

您可以根据以下语法根据其SDK(sdk),体系结构(arch)和/或配置(config)来对构建设置进行条件化:

1
2
3
BUILD_SETTING_NAME[sdk=sdk] = value for specified sdk
BUILD_SETTING_NAME[arch=architecture] = value for specified architecture
BUILD_SETTING_NAME[config=configuration] = value for specified configuration

如果在相同构建设置的多个定义之间进行选择,编译器将根据特性解析。

1
2
BUILD_SETTING_NAME[sdk=sdk][arch=architecture] = value for specified sdk and architectures
BUILD_SETTING_NAME[sdk=*][arch=architecture] = value for all other sdks with specified architecture

例如,您可以指定以下构建设置,仅通过编译活动体系结构来加速本地构建

1
ONLY_ACTIVE_ARCH[config=Debug][sdk=*][arch=*] = YES

包括来自其他配置文件的构建设置

构建配置文件可以使用与此功能所基于的等效C指令相同的#include语法包含来自其他配置文件的设置

1
#include "path/to/File.xcconfig"

正如我们将在本文后面看到的,您可以利用这一点以非常强大的方式构建构建设置的级联列表。

通常,当编译器遇到不能解析的#include指令时,它会引发一个错误。但是xcconfig文件也支持#include?指令,如果找不到文件不会报怨。

在很多情况下,您都不希望文件的存在或不存在来更改编译时行为;毕竟,构建在可预测的时候是最好的。但是您可以将其用作可选开发工具(如Reveal)的hook,这需要以下配置:

1
2
3
# Reveal.xcconfig
OTHER_LDFLAGS = $(inherited) -weak_framework RevealServer
FRAMEWORK_SEARCH_PATHS = $(inherited) /Applications/Reveal.app/Contents/SharedSupport/iOS-Libraries

创建构建配置文件

要创建构建配置文件,请选择File > New File菜单项(,向下滚动到标记为Other的部分,并选择配置设置文件模板。接下来,将其保存在项目目录中的某个位置,确保将其添加到所需的targets:

一旦创建了xcconfig文件,就可以将其分配给与其相关的目标的一个或多个构建配置。

构建配置文件不应该包含在任何项目目标中。如果你发现任何.xcconfig文件出现在你的应用程序的.ipa存档中,请确保它们不属于任何target,也不出现在任何Copy Bundle Resources的build phases。

既然我们已经介绍了使用Xcode构建配置文件的基础知识,现在让我们来看几个示例,看看如何使用它们来管理开发、阶段和生产环境。

创建构建配置文件

开发iOS应用程序通常需要在模拟器和测试设备(以及应用程序商店的最新版本,以供参考)上调整各种内部构建。

您可以使用xcconfig文件简化工作,这些文件为每个配置分配一个不同的名称和应用程序图标。

1
2
3
4
5
6
7
8
9
// Development.xcconfig
PRODUCT_NAME = $(inherited) α
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon-Alpha

//////////////////////////////////////////////////

// Staging.xcconfig
PRODUCT_NAME = $(inherited) β
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon-Beta

跨不同环境管理常量

如果您的后端开发人员根据前面提到的 12 Factor App原则进行配置,那么他们将为开发、阶段和生产环境提供独立的端点

在iOS上,管理这些环境最常见的方法可能是将条件编译语句与诸如DEBUG之类的构建设置一起使用。

1
2
3
4
5
6
7
import Foundation

#if DEBUG
let apiBaseURL = URL(string: "https://api.staging.example.com")!
#else
let apiBaseURL = URL(string: "https://api.example.com")!
#endif

这样就完成了工作,但是违反了代码/配置分离的规范。

另一种方法采用这些特定于环境的值,并将它们放入xcconfig文件中。

1
2
3
4
5
6
7
// Development.xcconfig
API_BASE_URL = api.staging.example.com

//////////////////////////////////////////

// Production.xcconfig
API_BASE_URL = api.example.com

xcconfig文件将序列//作为注释定界符,无论是否将其括在引号中。 如果尝试使用反斜杠\ / \ /进行转义,则这些反斜杠将按字面显示,并且必须从结果值中删除。 在指定每个环境的URL常量时,这尤其不便。

如果您不想解决这种不幸的行为,可以随时忽略该方案,并在代码中添加https://。 (您正在使用https…对吗?)

从Swift访问构建设置

由Xcode项目文件、xcconfig文件和环境变量定义的构建设置只在构建时可用。当您运行编译后的应用程序时,周围的上下文都不可用。(谢天谢地!)

但是等一下,你不记得以前在其他选项卡中看到过一些构建设置吗?

实际上,那个info选项卡只是target的info.plist的一个花哨的表示。在构建时,这些信息。plist文件根据提供的构建设置进行编译,并复制到最终的应用程序包中。因此,通过向$(API_BASE_URL)添加引用,您可以通过Foundation s Bundle API的infoDictionary属性访问这些设置的值。整洁!

按照这种方法,我们可能会执行以下操作:

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
29
import Foundation

enum Configuration {
    enum Error: Swift.Error {
        case missingKey, invalidValue
    }

    static func value<T>(for key: String) throws -> T where T: LosslessStringConvertible {
        guard let object = Bundle.main.object(forInfoDictionaryKey:key) else {
            throw Error.missingKey
        }

        switch object {
        case let value as T:
            return value
        case let string as String:
            guard let value = T(string) else { fallthrough }
            return value
        default:
            throw Error.invalidValue
        }
    }
}

enum API {
    static var baseURL: URL {
        return try! URL(string: "https://" + Configuration.value(for: "API_BASE_URL"))!
    }
}

当从调用站点查看时,我们发现这种方法与我们的最佳实践完美地协调在一起,看不到一个硬编码的常量。

1
2
3
let url = URL(string: path, relativeTo: API.baseURL)!
var request = URLRequest(url: url)
request.httpMethod = method

不要使用xcconfig文件来存储API密匙或其他凭证等机密信息,更多信息请参考我们关于iOS上的秘密管理的文章。

Xcode项目是单一的、脆弱的和不透明的。它们是团队成员之间协作的摩擦来源,通常也会拖累工作。

幸运的是,xcconfig文件花了很长时间来解决这些问题。将配置从Xcode移到xcconfig文件中会带来很多好处,并且提供了一种方法,可以使您的项目与Xcode的细节保持一定距离,而不必离开cupertino批准的愉快路径。

参考文章

Xcode Build Configuration Files