1. 为应用程序设计创建一个健壮的多环境构建设置

几乎所有的项目都需要多个环境。这允许我们针对非生产数据测试面向用户的软件,并部署后端开发中的实例以进行测试。

我们通常通过为每个项目提供不同的构建时配置来使用多个环境。有时还需要针对不同版本调整应用的外观和感觉,我们称之为皮肤或主题。我们通常也会在构建时实现这一点,通过注入包含颜色、字体、品牌图像等的主题文件。但是,这超出了本文的范围。

在本文中,我将讨论我们目前如何在iOS应用程序中支持多个环境。在之前的几次尝试都未能满足我们对健壮性和可靠性的要求之后,我们终于找到了这个应用设计解决方案。 我将解释其他解决方案的缺点,并强调我们强大的替代方案是如何在其他方案失败的地方发挥作用的。

你可能希望为每个环境定义不同的设置的一些例子是第三方库的API keys;或特定服务的不同url。

1.1. 最常见的替代方法

支持多种环境的一个常见解决方案是使用Xcode Configuration和Schemes来设计应用程序。这是该主题搜索结果顶部推荐的方法。

每个环境(例如开发,测试,UAT,登台,生产)都会创建一个Xcode Scheme。然后每个Scheme使用不同的构建配置。Every single Build Setting can be configured differently for each Configuration - including Architectures; Base SDK; whether On Demand Resources are enabled; whether Bitcode is enabled; which Info.plist to use; which Provisioning Profile to use; or what the Bundle Identifier will be.

这些设置中的任何一个,以及其他所有设置,都可以根据配置进行更改。在这样做的时候,你会冒这样的风险——充其量——在存档和发布内部构建之后,你无法在你的设备上安装应用程序。最糟糕的情况是,你可能会将一些不必要的设置发布到App Store中。

此外,如果你有一个合理的理由来为所有的“发布”环境更改一个特定的设置,那么你需要在多个地方更改它。

在Info.plist中定义的“支持的界面方向”(Supported Interface orientation)就是一个不需要的设置进入App Store的例子。这款应用程序可以通过所有级别的测试,视频可以验证其在园林下的正常工作。但当你安装你在App Store上发布的版本时,它不支持横屏播放视频。这是因为一个不同的info.Plist在测试中使用。

我们想要确保我们测试的Xcode Build Settings和我们发布应用时的设置是一样的。这种方法的一个明显优势是,它很容易在开发过程中通过改变Xcode中的Build Scheme来改变环境。

1.2. 共同的保障措施仍然有其缺点

开发人员通常会将每个配置的更改限制为用户定义的构建设置列表(尽管没有办法强制执行此限制)。

用户定义的构建设置可以从Info.plist中引用。 这样你就不必创建一个单独的信息。对于每个环境,你将有一个单独的默认信息。引用自定义设置的Plist。另外,您可以为每个支持的环境定义自定义设置。

但是,因为Xcode不能阻止其他构建设置的改变,所以你仍然有可能遇到上面描述的问题。

用户定义的构建设置的另一个缺点是,它们都被定义为字符串,没有验证所输入的内容。此外,因为所有设置都是以字符串形式输入的,所以需要将它们转换为实际感兴趣的数据类型,可能是布尔值或URL,我们需要编写重复的代码来转换每个设置。

因此,您可能有一个包含进入后端主入口点的URL的设置。如果这是一个无效的URL,不能解析创建一个NSURL实例,那么你不会发现,直到你运行应用程序。

在编译时知道这个错误不是很好吗?

1.3. 另一种替代

对于多环境问题,还有许多其他的应用程序设计解决方案。其中之一是使用GCC预处理宏。然而,这是一种混乱的方法,通常需要在配置设置文件(Configuration Settings File,又名.xcconfig)中定义宏用于开发,并从构建脚本中作为命令行参数传递以便发布。

它的设置有点混乱,但是可以在不需要多个配置的情况下完成。 这意味着你可以在默认的“Debug”配置上进行所有的开发,并通过在编译时注入基于预处理宏的设置,对“Release”配置执行所有级别的测试。

这种方法的一个主要缺点是,在开发过程中,我们通过在配置设置文件中注释掉来改变环境。然而,这并不是更改配置的理想方法。

1.4. 其它的缺点

上面描述的解决方案都要求您开发某种类型的AppEnvironment类(或结构)来从info.plist读取设置。或从另一个plist或平面文件,或从预处理器宏创建设置。 我所见过的该类的所有实现都包含其他逻辑,用于将字符串转换为各种数据类型,包括url。 最终,我们想要一个不需要手动实现该组件的解决方案。

每次添加新设置时,都需要将其添加到属性列表或平面文件中,并手动编辑AppEnvironment文件的代码,以添加一个属性来包装/隐藏对属性列表的访问,例如:

static var analyticsKey: String {
    let key = "analyticsKey"
    return plistDict[key]
}

1.5. 构建健壮的解决方案

所以在描述一个解决方案之前,让我们看看我们的需求:​

  • 在开发过程中在不同环境之间进行简单的切换,
  • 没有改变构建设置的风险,我们实际上不想在不同的环境中有所不同,
  • 确保我们测试的构建设置与我们发布的完全相同,
  • 如果缺少设置,则生成时错误,
  • 构建时验证设置的数据类型(例如URL, Bool, String),
  • 不需要将字符串转换为适当的数据类型,
  • 无需手动实现AppEnvironment类或结构,
  • 随着时间的推移,应该易于使用新设置进行扩展。

我们提出的解决方案满足了所有这些要求。它很容易设置,而且我们发现它在一些产品中非常健壮和可靠,具有简单的可扩展性。

Automatic Configuration Generator

我们已经创建了一个简单的工具——目前称为configen——它用于在构建时创建环境配置类。它具有以下特点:

  • 它将环境属性列表中的设置映射到环境配置类的静态属性,

  • 它使用映射文件来指定属性列表中每个设置的预期类型,这样类中可用的静态属性就是预期类型,

  • 如果属性缺失或其类型无效,它将产生构建错误,

  • 您可以为每个环境定义不同的属性列表。

该工具是作为一个Swift命令行脚本编写的,因此它可以验证属性数据类型,包括将字符串转换为NSURL实例。这个映射文件使用了Swift开发者熟悉的格式。

1.6. The mapping file:

在左侧提供要在环境配置类中实现的静态属性的名称。这也是属性列表文件中的键。在冒号之后,在右边,您指定要实现的属性的类型。

entryPoint: NSURL
searchPoint: NSURL
retryCount: Int
adUnitPrefix: String
pushKey: String
analyticsKey: String

1.7. The property list file:

avatar

1.8. The output

avatar

如果映射文件中定义的属性在属性列表中不存在,或者类型不匹配,则脚本将产生错误。

configen脚本:

这里提供了脚本本身,以及关于如何使用它的完整说明,以及它如何满足我们为它设定的所有需求。

1.9. Conclusion

我们现在有一个健壮的应用程序设计解决方案,用于多环境构建设置。乍一看,它似乎有点难以设置,但一旦设置到位,就使用多种配置所花费的时间而言,它可以提供大量的效率。特别是因为相关bug而节省的时间!

另外,您不需要将自己的类作为配置文件的接口来实现,这就抵消了额外的设置。总之,为人为错误提供更少的机会!