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

相關文章
相關標籤/搜索