前言
谈到多环境,相信现在大多公司都至少有2-3个app环境了,比如Test环境,UAT(User Acceptance Test)用户验收测试环境,Release环境等等。当需要开发打多个包的时候,一般常见做法就是直接代码里面修改环境变量,改完之后Archive一下就打包了。当然这种做法很正确,只不过不是很优雅很高效。如果搭建好了Jenkins(搭建教程),我们利用它来优雅的打包。利用Jenkins来打包,我们就需要给app来配置一下多个环境变量了。之后Jenkins分别在不同环境下自动集成即可。接下来,我们来谈谈常见的2种做法。
一.利用Build Configuration来配置多环境
前言里面我们先谈到了需求,由于需要配置多个环境,并且多个环境都需要安装到手机上,那么可以配置Build Configuration来完成这个任务。如果Build Configuration还不熟悉的,可以先温习一下官方文档,新版文档链接在这里Build settings reference。
1. 新建Build Configuration
先点击Project里面找到Configuration,然后选择添加,这里新加一个Configuration。系统默认是2个,一个Debug,一个Release。这里我们需要选择是复制一个Debug还是Release。Release和Debug的区别是,Release是不能调试程序,因为默认是屏蔽了可调试的一些参数,具体可以看BuildSetting里面的区别,而且Release编译时有做编译优化,会比用Debug打包出来的体积更小一点。
这里我们选择一个Duplicate “Debug” Configuration,因为我们新的环境需要debug,添加完了之后就会多了一套Configuration了,这一套其实是包含了一些编译参数的配置集合。如果此时项目里面有cocopods的话,打开Configuration Set就会发现是如下的样子:
在我们自己的项目里面用了Pod,打开配置是会看到如下信息
注意:刚刚新建完Build Configuration之后,这时如果有pod,请立即执行一下
pod install
pod安装完成之后会自动生成xcconfig文件,如果你手动新建这个xcconfig,然后把原来的debug和release对应的pod xcconfig文件内容复制进来,这样做是无效的,需要pod自己去生成xcconfig文件才能被识别到。
新建完Build Configuration,这个时候需要新建pod里面对应的Build Configuration,要不然一会编译会报错。如果没用pod,可以忽略一下这一段。
如下图新建一个对应之前Porject里面新建的Build Configuration
2. 新建Scheme
接下来我们要为新的Configuration新建一个编译Scheme。
新建完成之后,我们就可以编辑刚刚新建的Scheme,这里可以把Run模式和Archive都改成新建Scheme。如下图:
注意:如果是使用了Git这些协同工具的同学这里还需要把刚刚新建的Scheme共享出去,否则其他人看不到这个Scheme。选择“Manage Schemes”
3. 新建User-defined Build Settings
再次回到Project的Build Settings里面来,Add User-Defined Setting。
我们这里新加入2个参数,CustomAppBundleld是为了之后打包可以分开打成多个包,这里需要3个不同的Id,建议是直接在原来的Bundleld加上Scheme的名字即可。
CustomProductName是为了app安装到手机上之后,手机上显示的名字,这里可以按照对应的环境给予描述,比如测试服,UAT,等等。如下图。
这里值得提到的一点是,下面Pods的Build_DIR这些目录其实是Pods自己生成好的,之前执行过Pod install 之后,这里默认都是配置好的,不需要再改动了。
4. 修改info.plist文件 和 Images.xcassets
先来修改一下info.plist文件。
由于我们新添加了2个CustomAppBundleld 和 CustomProductName,这里我们需要把info.plist里面的Bundle display name修改成我们自定义的这个字典。编译过程中,编译器会根据我们设置好的Scheme去自己选择Debug,Release,TestRelease分别对应的ProductName。
我们还需要在Images.xcassets里面新添加2个New iOS App Icon,名字最好和scheme的名字相同,这样好区分。
新建完AppIcon之后,再在Build Setting里面找到Asset Catalog Compiler里面,然后把这几种模式下的App Icon set Name分别设置上对应的图标。如上图。
既然我们已经新建了这几个scheme,那接下来怎么把他们都打包成app呢??这里有一份官方的文档Troubleshooting Application Archiving in Xcode这里面详细记录了我们平时点击了Archive之后是怎么打包的。
这里分享一下我分好这些环境的心得。一切切记,每个环境都要设置好Debug 和 Release!千万别认为线上的版本只设置Release就好,哪天需要调试线上版本,没有设置Debug就无从下手了。也千万别认为测试环境的版本只要设置Debug就好,万一哪天要发布一个测试环境需要发Release包,那又无从下手了。我的建议就是每个环境都配置Debug 和 Release,即使以后不用,也提前设置好,以防万一。合理的设置应该如下图这样。
| -------------------------- |------------------|
| Scheme | Configurations |
| -------------------------- |------------------|
| XXXXProjectTest | Debug |
| |------------------|
| | Release |
| -------------------------- |------------------|
| XXXXProjectAppStore | Debug |
| |------------------|
| | Release |
| -------------------------- |------------------|
| XXXXProjectUAT | Debug |
| |------------------|
| | Release |
| -------------------------- |------------------|
注意这里一定要把Scheme的名字和编译方式区分开,选择了一个Scheme,只是相当于选择了一个环境,并不是代表这Debug还是Release。
我建议Scheme只配置环境,而进来的Run和Archive来配置Debug和Release,我建议每个Scheme都按照上图来,Run对应的Debug,Archive对应的Release。
配置好上述之后,就可以选择不同环境运行app了。可以在手机上生成不同的环境的app,可以同时安装。如下图。
5. 配置和获取环境变量
接下来讲几种动态配置环境变量的方法
1. 使用GCC预编译头参数GCC_PREPROCESSOR_DEFINITIONS
我们进入到Build Settings里面,可以找到Apple LLVM Preprocessing,这里我们可以找到Preprocessor Macros在这里,我们是可以加一些环境变量的宏定义来标识符。Preprocessor Macros可以根据不同的环境预先制定不同定义的宏。
如上图,圈出来的地方其实就是一个标识符。
有了这些我们预先设置的标识符之后,我们就可以在代码里面写入如下的代码了。
#ifdef DEVELOP
#define searchURL @"http://www.baidu.com"
#define sociaURL @"weibo.com"
#elif UAT
#define searchURL @"http://www.bing.com"
#define sociaURL @"twitter.com"
#else
#define searchURL @"http://www.google.com"
#define sociaURL @"facebook.com"
#endif
2. 使用plist文件动态配置环境变量
我们先来新建3个名字一样的plist作为3个环境的配置文件。
这里名字一样的好处是写代码方便,因为就只需要去读取“Configuration.plist”就可以了,如果名字不一样,还要分别去把对应环境的plist名字拼接出来才能读取。
众所周知,在一个文件夹里面新建2个相同名字的文件,Mac 系统都会提示我们名字相同,不允许我们新建。那我们怎么新建3个相同名字的文件呢?这其实很简单,分别放在3个不同文件夹下面即可。如下图:
我就是这样放置的,大家可以根据自己习惯去放置文件。
接下来我们要做的是在编译的时候,运行app前,动态的copy Configuration.plist到app里面,这里需要设置一个copy脚本。
进入到我们的Target里面,找到Build Phases,我们新建一个New Copy Files Phase,并且重命名为Copy Configuration Files。
echo "CONFIGURATION -> ${CONFIGURATION}"
RESOURCE_PATH=${SRCROOT}/${PRODUCT_NAME}/config/${CONFIGURATION}
BUILD_APP_DIR=${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app
echo "Copying all files under ${RESOURCE_PATH} to ${BUILD_APP_DIR}"
cp -v "${RESOURCE_PATH}/"* "${BUILD_APP_DIR}/"
这一段脚本就能保证我们的Configuration.plist 文件可以在编译的时候,选择其中一个打包进我们的app。
再写代码每次读取这个plist里面的信息就可以做到动态化了。
- (NSString *) readValueFromConfigurationFile {
NSBundle *bundle = [NSBundle mainBundle];
NSString *path = [bundle pathForResource:@"Configuration" ofType:@"plist"];
NSDictionary *config = [NSDictionary dictionaryWithContentsOfFile:path];
return config[@"serverURL"];
}
这里我假设plist文件里面预设置了一个serverURL的字符串,用这种方式就可以读取出来了。当然在plist里面也可以设置数组,字典,相应的把返回值和Key值改一下就可以了。
3. 使用单例来处理环境切换
当然使用一个单例也可以做到环境切换。新建一个单例,然后可以在设置菜单里面加入一个列表,里面列出所有的环境,然后用户选择以后,单例就初始化用户所选的环境。和上面几种方式不同的是,这种方式就是在一个app里面切换多种环境。看大家的需求,任取所需。