使用 Xcode Configuration 和 Scheme 管理多個 Build 環境

Photo by Heather Ford on Unsplash
Photo by Heather Ford on Unsplash
在開發專案時,我們需要建構多個版本,而每個版本會有個別的環境設定。例如,開發版本會連到開發用的 server,而 QA 版本會連到 QA 測試用的 server。本文章將介紹如何利用 Xcode 的 Configuration 和 Scheme 來建構多個版本。

在開發專案時,我們需要建構多個版本,而每個版本會有個別的環境設定。例如,開發版本會連到開發用的 server,而 QA 版本會連到 QA 測試用的 server。本文章將介紹如何利用 Xcode 的 Configuration 和 Scheme 來建構多個版本。

完整程式碼可以在 下載。

概覽

在本文章中,我們將會建立兩個版本,分別是 Develop 和 Production。Develop 版本是開發用的版本,而 Production 版本則是上架到 Apple Store 用的版本。每個版本都會有 Debug build 和 Release build。

建立 Configuration 檔案(.xcconfig)

首先,我們要先為 Develop 和 Production 版本建立個別的 configuration 檔案。在專案新增一個 Configuration Settings File。

Choose Configuration Settings File
Choose Configuration Settings File

輸入 Develop.xcconfig 作為檔案名稱。在紅色框內,不要選擇任何 targets,因為我們不希望將 Develop.xcconfig build 到 targets 裡面。建立好後,重複同樣步驟再建立一個 Configuration Settings File 叫 Production.xcconfig。

Develop 版本將會使用 Develop.xcconfig,而 Production 版本則會使用 Production.xcconfig。

Create Develop.xcconfig
Create Develop.xcconfig

新增 Builds

接下來,我們要對每個 build 指派 configuration 檔案。

Xcode 預設建立 Debug 和 Release builds。我們分別將 Debug 和 Release 重新命名為 DevDebug 和 DevRelease。

Rename Debug to DevDebug and Release to DevRelease.
Rename Debug to DevDebug and Release to DevRelease.

點擊 + 按鈕,選擇 Duplicate DevDebug Configuration,並命名為 ProDebug。再點擊一次,選擇 Duplicate DevRelease Configuration,並命名為 ProRelease。

Create ProDebug by duplicating DevDebug.
Create ProDebug by duplicating DevDebug.

所以,我們總共有四個 builds。

Four configurations: DevDebug, DevRelease, ProDebug, ProRelease.
Four configurations: DevDebug, DevRelease, ProDebug, ProRelease.

將 Develop.xcconfig 指派給 DevDebug 和 DevRelease,而 Production.xcconfig 指派給 ProDebug 和 ProRelease。

Set a configuration settings file to each configuration.
Set a configuration settings file to each configuration.

新增 Schemes

最後,我們要對每個 scheme 指派它的 Debug 和 Release builds。

點選 Manage Scheme

Select Manage Schemes.
Select Manage Schemes.

將現有的 ConfigurationExample scheme 重新命名為 Develop ConfigurationExample。就如其名,它是給 Develop 版本用的。

Rename ConfigurationExample scheme to "Develop ConfigurationExample" scheme.
Rename ConfigurationExample scheme to “Develop ConfigurationExample” scheme.

確認 Develop ConfigurationExample scheme 是使用 DevDebug 和 DevRelease builds。

"Develop ConfigurationExample" uses DebDebug and DevRelease configurations.
“Develop ConfigurationExample” uses DebDebug and DevRelease configurations.

再來,我們要新增一個 scheme 給 Production 版本使用。點選 New Scheme

Select New Scheme.
Select New Scheme.

輸入名稱 Production ConfigurationExample。

Create "Production ConfigurationExamples".
Create “Production ConfigurationExamples”.

新增好後,將 Production ConfigurationExample scheme 所使用的 builds,全部指派為 ProDebug 和 ProRelease builds。

"Product ConfigurationExample" uses ProDebug and ProRelease configurations.
“Product ConfigurationExample” uses ProDebug and ProRelease configurations.

在 Configuration 檔案中新增 Settings

現在讓我們在 configuration 檔案中新增新的 value 給一個 setting。

Configuration 檔案使用以下的格式:

<SettingName> = <SettingValue>

如果設定一個 value 給不存在的 setting,那就是宣告一個新的 setting。

NEW_SETTING = Hello Wayne's Talk

如果設定一個 value 給已存在的 setting,那它就會覆蓋 setting 原本的 value。我們還可以使用 $(inherited) 來取得該 setting 原本的 value。你可以在 Build settings reference 中找到更多預先定義好的 settings。

OTHER_SWIFT_FLAGS = $(inherited) -v

我們在 Develop.xcconfig 和 Production.xcconfig 中宣告兩個新個 settings:APP_NAME 和 APP_ENV。本文章接下來會介紹如何使用這兩個 settings。

// Develop.xcconfig
APP_NAME = Develop
APP_ENV = DEV
// Production.xcconfig
APP_NAME = Production
APP_ENV = PRO

對每個版本使用不同的 App 名稱

現在我們有兩個版本了。我們希望不同的版本顯示不同的 App 名稱。

TARGETS -> ConfigurationExample -> Build Settings 中,收尋 PRODUCT_NAME

Find Product Name in Build Settings.
Find Product Name in Build Settings.

將原本的值 $(TARGET_NAME) 改為 $(APP_NAME)APP_NAME 是我們剛剛在 configuration 檔案中新增的 setting。你可以試著執行 Develop ConfigurationExample 和 Production ConfigurationExample 來看看 App 名稱是否改變了。

Assign $(APP_NAME) to Product Name.
Assign $(APP_NAME) to Product Name.
Product Name is different in each builds.
Product Name is different in each builds.

對每個版本使用不同的 Bundle Identifier

我們有可能希望可以同時安裝所有的版本到一個 device 裡。所以我們必須對每個版本設定不同的 Bundle Identifier。

TARGETs -> ConfigurationExample -> Signing & Capabilities 中,我們發現每一個 build 都有個別的頁面。在 DevDebug 和 DevRelease 頁面中,設定 Bundle Identifier 為 com.waynestalk.ConfigurationExample.dev

Set Bundle Identifier to .dev for DevDebug and DevRelease.
Set Bundle Identifier to .dev for DevDebug and DevRelease.

在 ProDebug 和 ProRelease 頁面中,設定 Bundle Identifier 為 com.waynestalk.ConfigurationExample.pro。

Set Bundle Identifier to .pro for ProDebug and ProRelease.
Set Bundle Identifier to .pro for ProDebug and ProRelease.

切換到 All 頁面,可以看到所有的 Bundle Identifiers。

All Bundle Identifiers.
All Bundle Identifiers.

在程式中存取 Configuration 檔案中的 Settings

在程式中是無法直接存取 configuration 檔案中的 settings。不過,我們可以透過將一個 setting 新增到 Info。這樣一來,我們就可以在程式中存取了。

TARGETS -> ConfigurationExample -> Info 中,新增一個 key 名為 AppEnv,設定其 value 為 $(APP_ENV)APP_ENV 是我們剛剛在 configuration 檔案中新增的 setting。

Add APP_ENV setting to info.plist.
Add APP_ENV setting to info.plist.

然後,我們就可以在程式中存取 AppEnv,如下程式碼。

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let env = (Bundle.main.infoDictionary!["AppEnv"] as! String)
        print(env)
    }
}

對每個版本使用不同的資源檔

有時候我們需要在個別的版本中使用不同的資源檔,如 Firebase 的 GoogleService-Info.plist。

就如我們所知道的,configuration 檔案是個文字檔。所以最簡單的方式是,將所有版本的資源檔都加入到專案裡,並且都 build 進 app 裡。然後,在 configuration 檔案中對每個版本設定相對應的資源檔的路徑。這樣在 runtime 的時候,app 會讀取資源檔的路徑,再載入資源檔。

然而,上述的方式有一個缺點是,我們必須要將所有版本的資源檔都 build 進 app 裡面。

我們將介紹另一個方法。這個方法是,將所有版本的資源檔都加入到專案裡,但是不要 build 進 app 裡面。在編譯時候,再將相對應的資源檔複製到 build 資料夾。

以下我們將以 Firebase 的 GoogleService-Info.plist 為例子。

首先,先將 Develop 版本的 GoogleService-Info.plist 加入到專案,並且要勾選 Add to targets。

Add GoogleService-Info.plist to the project with adding to targets.
Add GoogleService-Info.plist to the project with adding to targets.

在專案的目錄下,新增 Develop/Production/ 資料夾。將 Develop 版本的 GoogleService-Info.plist 加入到 Develop/ 資料夾,並且不要勾選 Add to targets。再將 Production 版本的 GoogleService-Info.plist 加入到 Production/ 資料夾下,並且不要勾選 Add to targets

Add GoogleService-Info.plist to Develop/ and Production/ without adding to targets.
Add GoogleService-Info.plist to Develop/ and Production/ without adding to targets.

以上完成後,我們可以看到專案裡有三個 GoogleService-Info.plist。但是只有一個 GoogleService-Info.plist 在 Copy Bundle Resources 列表中。

Three GoogleService-Info.plist in the project, but only one in Copy Bundle Resources.
Three GoogleService-Info.plist in the project, but only one in Copy Bundle Resources.

TARGETS -> ConfigurationExample -> Build Phrases 下,點擊左上角的 + 按鈕,再點擊 New Run Script Phrase

Click New Run Script Phrase.
Click New Run Script Phrase.

將以下的程式碼貼上到 Run Script Phrase。在程式碼中,我們根據 ${CONFIGURATION},將相對應的 GoogleService-Info.plist 複製到 build 資料夾。

echo "Copy GoogleService-info.plist"
echo "Configuration is ${CONFIGURATION}"
set -x
case "${CONFIGURATION}" in
  "DevDebug" | "DevRelease" )
    cp "${PROJECT_DIR}/ConfigurationExample/Develop/GoogleService-info.plist" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-info.plist"
    ;;
  "ProDebug" | "ProRelease" )
    cp "${PROJECT_DIR}/ConfigurationExample/Production/GoogleService-info.plist" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-info.plist"
    ;;
  *)
    ;;
esac
Shell script to copy GoogleService-Info.plist.
Shell script to copy GoogleService-Info.plist.

最後讓我們試著編譯一下專案。我們可以看到,在編譯的時候,相對應的 GoogleService-Info.plist 被複製到 build 資料夾下。

Copy GoogleService-Info.plist to build folder when building the project.
Copy GoogleService-Info.plist to build folder when building the project.

結語

在開發專案時,我們常常需要有多個版本的環境設定。Xcode 的 configuration 和 scheme 讓我們輕鬆地做到。此外,將各個環境設定集中在 configuration 檔中,也比較好管理。

參考

1 comment
發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

You May Also Like
Photo by Alex Alvarez on Unsplash
Read More

Dispatch Queue 教學

GCD 提供有效率的並行處理,讓我們不需要直接管理多執行緒。它的 Dispatch Queues 可以循序地(serially)或是並行地(concurrently)執行任務。我們只需要將要並行的程式當作任務提交到 dispatch queues 就可以了。
Read More
Photo by Florinel Gorgan on Unsplash
Read More

如何製作一個 XCFramework

XCFramework 讓你可以將 iPhone、iPhone 模擬器等多的不同平台的二進位碼打包到一個可發佈的 .xcframework 檔。你只需要為你的 Framework 產生出一個 .xcframework 檔,就可以支援多種平台。
Read More
Photo by Fabian Gieske on Unsplash
Read More

SwiftUI @State & @Binding 教學

SwiftUI 推出了兩個 Property Wrapper – @State and @Binding。利用它們可以達到變數的 Two-way Binding 功能。也就是當變數的值改變時,它會重新被顯示。本章藉由製作一個 Custom View 來展示如何使用 @State 和 @Binding。
Read More
Photo by Svitlana on Unsplash
Read More

iOS:禁止螢幕截圖

基於一些理由,我們可能會想要禁止使用者對我們的 app 做螢幕截圖。然而,iOS 並沒有提供這樣的功能。所幸,我們可以利用 UITextField 來達到此效果。
Read More