Xcode Build 配置文件

做者:Mattt,原文连接,原文日期:2019-05-13 译者:雨谨;校对:numbbbbbWAMaker;定稿:Pancfhtml

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

了解每一个项目设置的功能以及它们之间如何交互,是一项须要多年磨练的技能。但 Xcode 将大部分的这类信息都都深埋在其图形化界面中,这对咱们没有任何好处。github

导航到项目编辑器的 "Build Settings" tab,你会看到分布在 project、target 和 configuration 上的 数百条 Build Setting(构建配置) —— 更别说其余六个 tab 了!编程

幸运的是,有一个更好的办法,没必要点击迷宫般的 tab 和箭头,就能够管理全部的配置。swift

这一周,咱们将向你展现如何在 Xcode 以外,经过修改基于文本的 xcconfig 文件,让你的项目更加紧凑、易懂、强大。后端


Xcode Build 配置文件,即你们所熟知的 xcconfig 文件,容许咱们在不使用 Xcode 的状况下声明和管理 APP 的 Build Setting。它们是纯文本,这意味着它们对代码管理系统更加友好,并且能够被任意编辑器修改。api

从根本上说,每一个配置文件都由一系列键值对组成,其语法以下:xcode

<#BUILD_SETTING_NAME#> = <#value#>
复制代码

例如,你可使用下面这样的 SWIFT_VERSION Build Setting,指定项目的 Swift 语言版本:安全

SWIFT_VERSION = 5.0
复制代码

根据 POSIX 标准,环境变量的名字由全大写字母、数字和下划线(_)组成 —— 经典例子就是 SCREAMING_SNAKE_CASE 🐍🗯。架构


乍一看,xcconfig 文件与 .env 文件有惊人的类似之处,它们的语法都很简单,都以换行分隔。可是,Xcode Build 配置文件的内容比表面上看到的要多。看哪!

保留现有值

要追加新内容,而不是替换现有定义时,能够像这样使用 $(inherited) 变量:

<#BUILD_SETTING_NAME#> = $(inherited)<#additional value#>
复制代码

这么作一般是为了搭建一些值的列表,好比编译器的 framework 头文件的搜索路径(FRAMEWORK_SEARCH_PATHS):

FRAMEWORK_SEARCH_PATHS = $(inherited) $(PROJECT_DIR)
复制代码

Xcode 按下面的顺序对 inherited 进行赋值(优先级从低到高):

  • 平台默认值(Platform Defaults)
  • Xcode 项目文件的 Build Setting(Xcode Project File Build Settings)
  • Xcode 项目的 xcconfig 文件(xcconfig File for the Xcode Project)
  • Active Target 的 Build Setting(Active Target Build Settings)
  • Active Target 的 xcconfig 文件(xcconfig File for the Active Target)

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

引用其余值

你能够按照下面的语法,经过其余设置的名字引用它们的值:

<#BUILD_SETTING_NAME#> = $(<#ANOTHER_BUILD_SETTING_NAME#>)
复制代码

这种引用既能够用于根据现有值定义新变量,也能够用于之内联方式动态构建新值。

OBJROOT = $(SYMROOT)
CONFIGURATION_BUILD_DIR = $(BUILD_DIR)/$(CONFIGURATION)-$(PLATFORM_NAME)
复制代码

条件约束

使用如下语法,你能够按 SDK(sdk)、架构(arch)和 / 或配置(config)对 Build Setting 进行条件约束:

<#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#>
复制代码

若是须要在同一 Build Setting 的多个定义之间进行选择,编译器将根据条件约束进行解析。

<#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#>
复制代码

例如,你可使用下面这条 Build Setting 指定仅编译 active architecture,从而提高本地 Build 的速度。

ONLY_ACTIVE_ARCH[config=Debug][sdk=*][arch=*] = YES
复制代码

引用其余配置文件中的设置

C 语言的 #include 指令同样,Build 配置文件也可使用这种语法来引用其余配置文件中的设置。

#include "<#path/to/File.xcconfig#>"
复制代码

正如咱们将在本文后面看到的,你能够利用这一点,以很是强大的方式搭建起 Build Setting 的级联列表。

正常来讲,当遇到一个没法解析的 #include 指令时,编译器会报错。可是 xcconfig 文件同时也支持 #include? 指令,在该指令下,若文件没法找到,编译器不会报错。

根据文件是否存在而改变编译时行为的状况并很少;毕竟,Build 最好是可预见的。可是你能够把它用在可选的开发工具上,好比 Reveal 须要如下的配置:

> # Reveal.xcconfig
复制代码

OTHER_LDFLAGS = (inherited) -weak_framework RevealServer
FRAMEWORK_SEARCH_PATHS =(inherited) /Applications/Reveal.app/Contents/SharedSupport/iOS-Libraries

建立 Build 配置文件

要建立 Build 配置文件,请选择 "File > New File..." 菜单项(N),下拉到 "Other" 部分,选中 Configuration Settings File 模板。将它保存到你的项目目录,并确保它在你指望的 target 上。

建立好 xcconfig 文件后,你就能够将它分配给对应 target 的一个或多个 Build 配置。


如今咱们已经介绍了 Xcode Build 配置文件使用的基础知识,那么让咱们来看几个示例,看看如何使用它们来管理 development、stage 和 production 环境。


为内部版本提供自定义的 APP 名称和图标

开发 iOS 应用程序时,一般须要在模拟器和测试设备上安装各类内部版本(同时也会安装应用程序商店的最新版本,以供参考)。

使用 xcconfig 文件,你能够轻松地为每一个配置分配一个不一样的名称和 APP 图标。

// 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 理论,那么他们将为 development、stage 和 production 环境提供单独的接口。

iOS 上最多见的环境管理方式可能就是使用条件编译语句 + DEBUG 这样的 Build Setting 了。

import Foundation

#if DEBUG
let apiBaseURL = URL(string: "https://api.example.dev")!
let apiKey = "9e053b0285394378cf3259ed86cd7504"
#else
let apiBaseURL = URL(string: "https://api.example.com")!
let apiKey = "4571047960318d233d64028363dfa771"
#endif
复制代码

这只是完成了任务,可是与代码 / 配置分离的标准相冲突。

另外一个方案是将这些与环境相关的值放到它们该待的地方 —— xcconfig 文件中。

// Development.xcconfig
API_BASE_URL = api.example.dev
API_KEY = 9e053b0285394378cf3259ed86cd7504

---

// Production.xcconfig
API_BASE_URL = api.example.com
API_KEY = 4571047960318d233d64028363dfa771
复制代码

不幸的是,xcconfig 将全部 // 都当成注释分隔符,无论它们是否包括在引号中。若是你用反斜线 \/\/ 进行转义,那么这些反斜线也将被直接展现出现,使用时必须从结果中移除。在指定每一个环境的 URL 常量时,这尤为不方便。

若是不想处理这种麻烦的事情,你能够在 xcconfig 中忽略 scheme,而后在代码中添加 https://(你是在使用 https……对吧?)

然而,要以编程方式获取这些值,咱们还须要一个额外的步骤:

在 Swift 中访问 Build Setting

由 Xcode 项目文件、xcconfig 文件和环境变量定义的 Build Setting 只在 Build 时可用。当你运行一个已经编译的 APP 时,全部相关的上下文都是不可见的。(谢天谢地!)

可是等一下——你不记得以前在其余 tab 中看到过一些 Build Setting 吗?Info,是吗?

实际上,Info tab 只是 target 的 Info.plist 文件的一个马甲。Build 时,这个 Info.plist 文件会根据 Build Setting 的配置进行编译,而后复制到最终 APP 的 bundle 中。所以,添加 $(API_BASE_URL)$(API_KEY) 的引用后,你能够经过 Foundation Bundle API 的 infoDictionary 属性访问这些值。完美!

按照这种方法,咱们能够作以下工做:

import Foundation

enum Configuration {
    static func value<T>(for key: String) -> T {
        guard let value = Bundle.main.infoDictionary?[key] as? T else {
            fatalError("Invalid or missing Info.plist key: \(key)")
        }

        return value
    }
}

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

    static var key: String {
        return Configuration.value(for: "API_KEY")
    }
}
复制代码

从调用的角度考虑,咱们发现这种方法与咱们的最佳实践完美地在结合一块儿 —— 没有出现一个硬编码的常量!

let url = URL(string: path, relativeTo: API.baseURL)!
var request = URLRequest(url: url)
request.httpMethod = method
request.addValue(API.key, forHTTPHeaderField: "X-API-KEY")
复制代码

不要把私密的东西写在代码中。相反,应该将它们安全地存储在密码管理器或相似的东西中。

为了防止你的私密被泄漏到 GitHub 上,请将下列配置添加到你的 .gitignore 文件中(根据须要):

> # .gitignore
复制代码

Development.xcconfig Staging.xcconfig Production.xcconfig

一些开发人员喜欢使用包含了所需 key 的占位符文件(例如 Development.sample.xcconfig)代替这些文件。拉取代码时,开发人员再将该文件复制到非占位符位置,并相应地填充它。



Xcode 项目是庞大、脆弱的和不透明的。它们是团队成员合做时摩擦的来源,也经常是工做的累赘。

幸运的是,xcconfig 文件很好地解决了这些痛点。将配置从 Xcode 移到 xcconfig 文件带来了不少好处,可让你的项目与 Xcode 的细节保持必定距离,不受苹果公司的掣肘。

本文由 SwiftGG 翻译组翻译,已经得到做者翻译受权,最新文章请访问 swift.gg

相关文章
相关标签/搜索