Xcode 项目多环境配置最佳实践

3,567 阅读8分钟

您根据不同的环境配置了哪些内容?您可能具有仅用于调试的视图,或者您可能希望关闭发布版本的日志记录。您可能有多个后端环境可配置为 dev,QA,UAT,stage,prod 等。其中每个都需要不同的 root url,api key和app secret。该应用程序还可能与社交媒体,崩溃报告工具或其他分析工具集成,我们不应该通过我们的测试工作污染这些数据。我们可能还希望更改应用程序图标和应用程序名称,以使其显示已安装的应用程序正在运行的环境。

开发简单的 iOS 应用程序并不用担心配置太多,这很容易。当你刚刚开始时,可以使用代码进行一些设置,根据需要修改值。您甚至可以尝试注释/取消注释代码行以在不同配置之间切换。有些人使用#if DEBUG。这些中的任何一个都会很快成为问题。它容易出错并且非常耗时。那么我们该怎么办?

让我们来看一个设置具有多个环境的项目的示例。我只会做两个,但可以根据需要重复这些步骤。

建立

您可以使用现有项目或创建新项目。单个视图应用程序适用于此演示。我称之为“EnvironmentsTest”。默认情况下,您将拥有一个 scheme 和两个 configurations(debug和release)。让我们从一个结构开始,以保存我们的可配置属性。

struct Config {
    let scheme: String
    let host: String
    let key: String
    
    init() {
        scheme = "https"
        host = "api.testapp.com"
        key = "key.testapp.prod"
    }
}
extension Config {
    static var current: Config = Config()
}

为了测试这一点,我们可以在应用程序完成启动时打印配置。注意:不要忘记删除它,您不希望您的应用程序在生产中泄露此信息。

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {        
    print(Config.current)
    return true
}

在控制台中,您将看到期望的配置被打印出来。

Config(scheme: "https", host: "api.testapp.com", key: "key.testapp.prod")

Debug & Release vs. Dev & Prod

当你只有两个环境担心时,几乎可以自然地将调试分配给 dev 并释放到 prod。我已经看过很多了。不幸的是,这不起作用。 Dev 和 prod 表示环境配置,而 debug 和 release 是构建配置。您应该能够在某种程度上混合和匹配它们。例如,您可能想要调试 prod 构建。您永远不会发布开发构建,但您可能想要分析开发构建,我们通常使用发布配置。当然,当您想要添加更多环境时,您将完全崩溃。

通过 info.plist 注入配置

我们可以在 Info.plist 文件中创建可以读入的条目来替换默认配置。

<key>Config</key>
	<dict>
		<key>scheme</key>
		<string>http</string>
		<key>host</key>
		<string>dev-api.testapp.com</string>
		<key>key</key>
		<string>key.testapp.dev</string>
	</dict>

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/8/27/16cd0ea60c980701~tplv-t2oaga2asx-image.image

然后更改 Config 扩展名以读取它。直接使用 infoDictionary 可能很诱人,特别是当我们只有三个值时,但随着你的配置变得越来越复杂,拥有可解码的东西会更好。

struct ConfigContainer: Decodable {
    let Config: Config
}

extension Config: Decodable {
    static var current: Config = {
        guard let infoURL = Bundle.main.url(forResource: "Info", withExtension: "plist") else { fatalError("No info.plist in main bundle") }
        do {
            let infoData = try Data(contentsOf: infoURL)
            let decoder = PropertyListDecoder()
            let item = try decoder.decode(ConfigContainer.self, from: infoData)
            return item.Config
        } catch {
            return Config()
        }
    }()
}

现在我们可以看到开发环境的配置。

Config(scheme: "http", host: "dev-api.testapp.com", key: "key.testapp.dev")

所以,现在我们通过 info.plist 注入配置,这是朝着正确方向迈出的一步。现在,您如何在这些环境之间切换?我们需要一种方法来定义所有配置,然后能够在它们之间进行选择。

Targets

当开发人员希望在同一设备上安装多个版本(不同环境)的应用程序时,他们通常会添加 target。大多数情况下,添加target 是多余的。添加 target 时,您必须记住设置每个 target 的设置。例如,如果要在应用程序中启用摄像头访问,则必须单独为每个 target 设置所需的 Info.plist。此外,无论何时向项目添加文件,都必须确保将其正确添加到每个 target。

我们真正追求的是从下拉列表中选择环境/配置并运行它。schemes 是一种很好的方法。首先,我们需要设置一些构建配置。

Build Configurations

我们从 debug 和 release 配置开始。让我们将这些与我们的生产环境相结合。只需将 Debug 重命名为 Prod-Debug 并将其发布到 Prod-Release。接下来,创建一个新配置,复制 Prod-Debug。重命名新配置 Dev-Debug。

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/8/27/16cd0ea6186b2fe8~tplv-t2oaga2asx-image.image

在大多数情况下,您不需要复制发布版本。为每个环境执行调试和发布会变得混乱且难以维护。我建议只为您要分析或分发的环境添加发布配置。

Schemes

现在已经建立了构建配置,我们可以创建 scheme。单击方案下拉列表,然后选择 “Edit Scheme…”

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/8/27/16cd0ea61ae8a7c4~tplv-t2oaga2asx-image.image

选择左下角的**“Duplicate Scheme”**按钮。命名新方案以指明target和环境;我称之为“EnvironmentsTest - Dev”.

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/8/27/16cd0ea6150d026f~tplv-t2oaga2asx-image.image

确保 scheme 是选中 shared 的。即使您不与团队合作,也可以确保在移动到另一台计算机时保存设置。 对于左侧的每种类型的构建**(Run, Test, Profile, Analyze)** ,选择Info选项卡,然后将Build Configuration设置为Dev-Debug。 如果您为此环境创建了发布配置,则应将其用于 Profile。在 Debug 配置中进行性能分析的后果超出了本文的范围,在某些情况下可以,只知道在 Release 中可能会有不同的结果。 我总是把 Archive 设置为 Prod-Release。这样,无论选择何种方案,archive build都将为 Prod 构建。然后我不必担心意外上传 Dev 版本。最终,最好依靠其他工作流程工具来防止这样的错误,但这是另一话题了。

完成后按关闭按钮。

此时,我们添加了构建配置和scheme,以便为不同的环境构建。但是,如果您运行该应用程序,则每个环境都会得到相同的结果。我们如何实际定制它?

配置设置文件

配置设置文件是设置每个环境、配置设置的好地方。转到File -> New -> File… 或按⌘N。向下滚动到“Other”部分,然后选择Configuration Settings File。按Next并将其命名为 Dev.xcconfig 和Create。输入或复制以下内容:

scheme = http
host = dev-api.testapp.com
key = key.testapp.dev

然后返回到 Info.plist 文件并使用以下内容替换 Config 部分。

<key>Config</key>
	<dict>
		<key>scheme</key>
		<string>$(scheme)</string>
		<key>host</key>
		<string>$(host)</string>
		<key>key</key>
		<string>$(key)</string>
	</dict>

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/8/27/16cd0ea6376b3177~tplv-t2oaga2asx-image.image

最后,返回创建项目配置的位置。您会注意到右栏是Based on Configuration File。修改 Dev-Debug 项目以使用Dev配置。

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/8/27/16cd0ea6158738d4~tplv-t2oaga2asx-image.image

现在,当您使用 Dev 方案运行项目时,您将看到 Dev 配置的结果,当您使用 Prod 方案运行项目时,您将看到一个空配置。那么,我们应该为 Prod 构建创建另一个配置设置文件。但是,由于曝光,我不喜欢这种解决方案。我建议我们以不同的方式处理 prod。

Prod config

如果您要将其分发给公众,Info.plist 不是放置任何应用程序机密的正确位置。因此,我们需要找到另一种配置 Prod 的方法。真正保护这些值超出了本文的范围,但一个好的开始是将这些值从 Info.plist 中移出并转换为已编译的代码。如果你回顾我们的Config结构,它已经存在了。我们所要做的就是从 Info.plist 中删除(空)值,代码应加载我们的默认配置,即Prod。

从 Project Navigator 中选择项目,选择目标并转到Build Phases选项卡。单击**+按钮,然后选择“New Run Script Phase”**。单击标题将其重命名为不太通用的名称。我将标题设置为“Remove Prod Config”,将以下内容复制到脚本区域。

PLISTBUDDY="/usr/libexec/PlistBuddy"
INFO_PLIST="${TARGET_BUILD_DIR}/${INFOPLIST_PATH}"
if [[ -z ${host} ]]; then
$PLISTBUDDY -c "Delete :Config" "${INFO_PLIST}" || true
fi

这就是说,如果“host”变量为空(或不存在),则从 Info.plist 文件中删除 Config 对象。

注意:您必须执行**clean build(⌘⇧K)**以强制build phase运行。

现在,您将在每个 scheme 中获得正确的结果! 🎉

App Transport Security

您可能已经意识到,Apple 现在需要应用程序来支持最佳实践 HTTPS 安全性。对于每个环境都遵循此规则通常是个好主意,即使在开发中也是如此。但是,您不可能总是这样做(例如,在准备“本地”环境时)或者可能尚未配置,但您仍需要继续开发。我们可以通过另一个Run Script Phase处理这个问题。将此命名为“Http - Allows Arbitrary Loads”并将以下内容复制到脚本区域。

PLISTBUDDY="/usr/libexec/PlistBuddy"
INFO_PLIST="${TARGET_BUILD_DIR}/${INFOPLIST_PATH}"
if [ ${scheme} == "http" ]; then
$PLISTBUDDY -c "Add :NSAppTransportSecurity dict" "${INFO_PLIST}"
$PLISTBUDDY -c "Add :NSAppTransportSecurity:NSAllowsArbitraryLoads bool true" "${INFO_PLIST}" || true
$PLISTBUDDY -c "Set :NSAppTransportSecurity:NSAllowsArbitraryLoads true" "${INFO_PLIST}"
else
$PLISTBUDDY -c "Delete :NSAppTransportSecurity:NSAllowsArbitraryLoads" "${INFO_PLIST}" || true
fi

通过在application(_:didFinishLaunchingWithOptions:)打印值来测试每个 scheme.

print(String(describing: Bundle.main.infoDictionary?["NSAppTransportSecurity"]))

非常好,不是吗?

并行运行 Dev 和 Prod

有时在同一设备上安装Dev build和Prod build很方便,这样你就可以在两者之间切换。要实现这一点,您所要做的就是更改包ID。您还需要一种简单的方法来确定哪一个是哪个。为此,我们可以更改显示名称和图标。

注意:对于所有这些设置,我始终保持生产版本的构建;这是进入App Store的构建,不应修改。由于这个规则,它也很容易识别,因为它没有任何修饰符。

Bundle Identifier

要更改Bundle Identifier,请转到 target 的build settings并搜索“bundle identifier”,然后单击箭头以展开所有配置。我通常会附加一个“ - ”后跟配置名称。

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/8/27/16cd0ea675dec9f4~tplv-t2oaga2asx-image.image

Display Name

这些步骤与Bundle Identifier相同,但这次搜索“product name”。为此,我建议用环境替换名称。否则,文本可能太长而无法使用。

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/8/27/16cd0ea6af622079~tplv-t2oaga2asx-image.image
您还可以使用显示名称中的版本号快速确定安装的版本。

Icon

更改图标几乎同样容易。在同一屏幕上,搜索“Icon”。您应该看到“Asset Catalog App Icon Set Name.”。再次,添加一个破折号,后跟配置名称。

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/8/27/16cd0ea7045e6260~tplv-t2oaga2asx-image.image
现在,在 asset catalog 中为每个配置创建图标集。

总结

每个配置执行所有这些步骤似乎需要做很多工作才能更改一些设置,但这非常值得。当然,它需要一些时间来完成所有设置,但完成后切换变得微不足道。花时间做正确的事,从长远来看,你将节省时间。您可以通过在环境之间快速切换来节省时间。你可以省去手动操作时犯错误的麻烦。