一些初学者可能会好奇,为什么在开发应用的时候我们需要用两套隔离的数据库和环境。这是因为在你持续地开发应用或增加新特性的时候,可能希望将开发版本和已经存在的生产版本的应用进行区分。标准的开发实践是针对不同版本的软件使用不同的环境,而对我们来说,这个软件就是 iPhone 应用。一般来讲,开发版应用使用的数据库(或其它系统,比如说统计分析系统)应该与生产版应用进行区分。在测试或者开发环境中,我们经常使用到类似 “test comment”, “argharghargh” 和 “one more test comment” 这样的测试数据。很显然,我们并不想让真实的用户看到这样的信息。如果应用使用了统计分析系统,在测试阶段我们可能会发送成千上万次的统计事件。我们当然也不想让这样的数据跟真实的统计数据混在一起。这就是为什么我们应该区分开发环境和生产环境。
当使用不同的环境开发时,应用需要知道它当前使用的环境。一种通用的做法是在程序的 AppDelegate 中定义一个可以将应用初始化为开发或者生产模式的全局变量。
enumenvironmentType{
casedevelopment, production
}
letenvironment:environmentType= .production
switchenvironment {
case.development:
//setweb service URLtodevelopment
//setAPIkeystodevelopment
print("It's for development")
case.production:
//setweb service URLtoproduction
//setAPIkeystoproduction
print("It's for production")
}
这种方法要求我们在切换环境的时候改变全局变量的值。虽然这种方法很方便也很快捷,但是它存在很大的局限性。首先,因为在开发与生产环境中都使用了同一个 bundle ID,我们就不能在同一个设备上同时安装应用的不同版本。再者,这种方法有可能导致我们不小心把开发版本的应用提交到 App Store 上。如果忘记切换全局变量的值,我们就会提交错误版本的应用。我曾经就有一次在提交应用之前忘记改变全局变量的值,结果用户就使用到了我的开发版本。这实在是太糟糕了。
在本篇文章中我将会展示一种更好的方法来区分开发和生产的构建版本。更确切地来说,我们将会在 Xcode 中创建一个开发版的 target。这个方法同时适用于新建的或现存的大型项目,所以你可以使用一个现有的项目来跟着这个教程进行实践。
使用这种方法,生产版和开发版的应用都会使用同一套基础代码,但是可以有不同的图标、bundle ID 以及不同的数据库。分发和提交应用的过程也会十分简单。更重要地,应用的测试人员和管理人员可以在同一个设备上同时安装应用的两个版本,这样他们就可以更加清楚地了解他们现在试用的是哪一个版本。
如何创建新的 Target
那么我们如何在 Xcode 当中新建一个开发版的 target 呢?我会使用我提供的示例项目 “todo” 来一步一步地进行演示。你可以使用自己的项目,并跟随这些步骤进行操作:
1. 在 Project Navigator 面板上选择项目,进入设置。在Targets小节下面,右击现有的 target 并且选择Duplicate来对一个现在的 target 进行复制。
2. Xcode 会询问你这个 target 是不是针对于 iPad 开发的。对于本教程来说,我们只需要选择 “Duplicate Only” 就可以了。
提示:如果你的项目支持通用设置,则 Xcode 不会提示上面的消息。
3. 现在我们有了一个名为todo copy的新 target 和构建 scheme 。让我们来重命名一下以便于区分。
在TARGETS列表中选择新创建的 target。按下回车键来对它的名称进行编辑,并且给它起一个合适的名称。我倾向于使用 “todo Dev”。当然你可以使用你喜欢的任何名字。
接下来,来到 “Manage Schemes…”,选择在第 1 步中新建的 scheme,并按下回车键。把新 scheme 命名为跟新 target 一样的名字(�即我们上一小步中使用的名字)
4. 这一步是可选的,不过我强烈建议进行这一步。如果想让开发版跟生产版的应用更加容易区分,我们应该提供不同的应用图标和启动界面。这可以使测试更加清楚他们现在使用的版本,并且可以防止我们提交开发版本的应用到商店中。 :grinning:
选中Assets.xcassets然后添加新的 App 图标。选择 icon > App Icons & Launch Images > New iOS App Icon. 将新图标命名为 “AppIcon-Dev” 并且添加你需要的图片。
5. 现在回到项目设置,选择开发版的 target 然后修改 bundle ID。可以简单地在原来的 ID 上添加一个 “Dev” 后缀。如果有操作过第 4 步,在这里确保你的 app 图标设置为在上一步中添加的图标。
6. Xcode 会自动为新的 target 创建一个 plist 文件(一般命名为 todo copy-Info.plist)。可以在项目的根目录下找到这个文件。将 “copy” 修改为 “Dev”,然后将它放到原来的 plist 文件下方。这样可以更方便我们对文件进行操作。
7. 现在选择开发版 target 下的 “Build Settings”,滚动到 “Packaging”,然后修改指定 plist 文件为开发版 plist(即刚刚的 todo Dev.plist)。
8. 最后,为生产版和开发版 target 同时设置一个预处理宏和编译器标志。这样在之后的开发中我们就可以在代码中使用这个标识来检测当前运行的应用是哪个版本。
对于 Objective-C 项目,来到Building Settings,滚动到Apple LLVM 7.0 - Preprocessing。展开Preprocessor Macros并且为Debug和Release添加一个变量。对于开发版 target(即 todo Dev),设置变量的值为DEVELOPMENT=1(校对注:等号两边不能有空格)。相对地,设置开发版 target 的值为DEVELOPMENT=0。
对于 Swift 项目,编译器不再支持预处理指令了。相对地,它使用了运行期属性(compile-time attributes)和构建配置。为了增加开发版构建的标志,选择开发 target。来到Build Settings,向下滚动到Swift Compiler - Custom Flags小节。设置值为-DDEVELOPMENT来表明当前为 target 为开发版。
现在我们已经创建并配置好了开发版的 target,接下来要做什么呢?
使用 Target 和宏
因为设置过了DEV_VERSION的宏,我们就可以在代码中利用这个宏对项目使用动态检测了。这里有一个简单的使用示例:
Objective-C:
#ifDEVELOPMENT
#defineSERVER_URL @"http://dev.server.com/api/"
#defineAPI_TOKEN @"DI2023409jf90ew"
#else
#defineSERVER_URL @"http://prod.server.com/api/"
#defineAPI_TOKEN @"71a629j0f090232"
#endif
在 Objective-C 当中,我们可以使用#if来对DEVELOPMENT的状态进行检测,并且为 URL/API 设置对应的值。
Swift:
#ifDDEVELOPMENT
letSERVER_URL="http://dev.server.com/api/"
letAPI_TOKEN="DI2023409jf90ew"
#else
letSERVER_URL="http://prod.server.com/api/"
letAPI_TOKEN="71a629j0f090232"
#endif
在 Swift 当中,我们依然可以使用#if来对构建配置进行动态判断。然而,我们不再使用#define来定义一个常量,而是简单地使用let来定义一个 Swift 中全局常量。
提示:一般情况下,我们会将上面的代码放在 AppDelegate 当中。不过最终取决于你想在哪里初始化应用的配置。
当选择 “todo Dev” 的 scheme 并运行项目,我们就会创建一个开发版的构建并自动将服务器配置设置为开发版的环境。你现在可以放心地上传这个开发版到 TestFlight 或者 HockeyApp 让应用的测试人员或者管理人员进行测试了。
以后如果需要创建一个生产版的构建,只要简单地选择 “todo” 的 scheme。一行代码都不需要改变。
关于管理多个 Target 的一些注意事项
1. 当为项目添加新文件时,不要忘记同时勾选多个 target 来保持各个 target 中的代码一致。
2. 如果你的项目中使用了 Cocoapods,不要忘记在 podfile 中增加新的 target。我们可以使用link_with来指定多个 target。可以在Cocoapods 文档中找到更详细的内容。现在 Podfile 看起来应该是这样的:
source'https://github.com/CocoaPods/Specs.git'
platform:ios,'7.0'
workspace'todo'
link_with'todo','todo Dev'
pod'Mixpanel'
pod'AFNetworking'