手把手教你给一个iOS app配置多个环境变量

前言

谈到多环境,相信如今大多公司都至少有2-3个app环境了,好比Test环境,UAT(User Acceptance Test)用户验收测试环境,Release环境等等。当须要开发打多个包的时候,通常常见作法就是直接代码里面修改环境变量,改完以后Archive一下就打包了。固然这种作法很正确,只不过不是很优雅很高效。若是搭建好了Jenkins(搭建教程),咱们利用它来优雅的打包。若是利用Jenkins来打包,咱们就须要来给app来配置一下多个环境变量了。以后Jenkins分别再不一样环境下自动集成便可。接下来,咱们来谈谈常见的2种作法。html

目录

  • 1.利用Build Configuration来配置多环境
  • 2.利用xcconfig文件来配置多环境
  • 3.利用Targets来配置多环境

一.利用Build Configuration来配置多环境

前言里面咱们先谈到了需求,因为须要配置多个环境,而且多个环境都须要安装到手机上,那么能够配置Build Configuration来完成这个任务。若是Build Configuration还不熟悉的,能够先温习一下官方文档ios

1. 新建Build Configuration

先点击Project里面找到Configuration,而后选择添加,这里新加一个Configuration。系统默认是2个,一个Debug,一个Release。这里咱们须要选择是复制一个Debug仍是Release。Release和Debug的区别是,Release是不能调试程序,由于默认是屏蔽了可调试的一些参数,具体能够看BuildSetting里面的区别,并且Release编译时有作编译优化,会比用Debug打包出来的体积更小一点。git

这里咱们选择一个Duplicate “Debug” Configuration,由于咱们新的环境须要debug,添加完了以后就会多了一套Configuration了,这一套实际上是包含了一些编译参数的配置集合。若是此时项目里面有cocopods的话,打开Configuration Set就会发现是以下的样子:github

在咱们本身的项目里面用了Pod,打开配置是会看到以下信息数组

注意:刚刚新建完Build Configuration以后,这时若是有pod,请当即执行一下xcode

pod install复制代码

pod安装完成以后会自动生成xcconfig文件,若是你手动新建这个xcconfig,而后把原来的debug和release对应的pod xcconfig文件内容复制进来,这样作是无效的,须要pod本身去生成xcconfig文件才能被识别到。网络

新建完Build Configuration,这个时候须要新建pod里面对应的Build Configuration,要否则一会编译会报错。若是没用pod,能够忽略一下这一段。app

以下图新建一个对应以前Porject里面新建的Build Configurationjsp

2. 新建Scheme

接下来咱们要为新的Configuration新建一个编译Scheme。ide

新建完成以后,咱们就能够编辑刚刚新建的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里面切换多种环境。看你们的需求,任取所需。

二.利用文件来配置多环境

说道xcconfig,这个官方文档上面也提到的不是很详细,在网上寻找了一下,却是找到了另一份详细非官方文档。The Unofficial Guide to xcconfig files

提到xcconfig,就要先说说几个概念。

1. 区分几个概念

先来区分一下Xcode Workspace、Xcode Scheme、Xcode Project、Xcode Target、Build Settings 这5者的关系。这5者的关系在苹果官方文档上其实都已经说明的很清楚了。详情见文档Xcode Concepts

我来简单来解读一下文档。

Xcode Workspace

A workspace is an Xcode document that groups projects and other documents so you can work on them together. A workspace can contain any number of Xcode projects, plus any other files you want to include. In addition to organizing all the files in each Xcode project, a workspace provides implicit and explicit relationships among the included projects and their targets.

workspace这个概念你们应该都很清楚了。它能够包含多个Project和其余文档文件。

Xcode Project

An Xcode project is a repository for all the files, resources, and information required to build one or more software products. A project contains all the elements used to build your products and maintains the relationships between those elements. It contains one or more targets, which specify how to build products. A project defines default build settings for all the targets in the project (each target can also specify its own build settings, which override the project build settings).

project就是一个个的仓库,里面会包含属于这个项目的全部文件,资源,以及生成一个或者多个软件产品的信息。每个project会包含一个或者多个 targets,而每个 target 告诉咱们如何生产 products。project 会为全部 targets 定义了默认的 build settings,每个 target 也能自定义本身的 build settings,且 target 的 build settings 会重写 project 的 build settings。

最后这句话比较重要,下面设置xcconfig的时候就会用到这一点。

Xcode Project 文件会包含如下信息,对资源文件的引用(源码.h和.m文件,frame,资源文件plist,bundle文件等,图片文件image.xcassets还有Interface Builder(nib),storyboard文件)、文件结构导航中用来组织源文件的组、Project-level build configurations(Debug\Release)、Targets、可执行环境,该环境用于调试或者测试程序。

Xcode Target

A target specifies a product to build and contains the instructions for building the product from a set of files in a project or workspace. A target defines a single product; it organizes the inputs into the build system—the source files and instructions for processing those source files—required to build that product. Projects can contain one or more targets, each of which produces one product.

target 会有且惟一辈子成一个 product, 它将构建该 product 所需的文件和处理这些文件所需的指令集整合进 build system 中。Projects 会包含一个或者多个 targets,每个 target 将会产出一个 product。

这里值得说明的是,每一个target 中的 build setting 参数继承自 project 的 build settings, 一旦你在 target 中修改任意 settings 来重写 project settings,那么最终生效的 settings 参数以在 target 中设置的为准. Project 能够包含多个 target, 可是在同一时刻,只会有一个 target 生效,可用 Xcode 的 scheme 来指定是哪个 target 生效。

Build Settings

A build setting is a variable that contains information about how a particular aspect of a product’s build process should be performed. For example, the information in a build setting can specify which options Xcode passes to the compiler.

build setting 中包含了 product 生成过程当中所需的参数信息。project的build settings会对于整个project 中的全部targets生效,而target的build settings是重写了Project的build settings,重写的配置以target为准。

一个 build configaration 指定了一套 build settings 用于生成某一 target 的 product,例如Debug和Release就属于build configaration。

Xcode Scheme

An Xcode scheme defines a collection of targets to build, a configuration to use when building, and a collection of tests to execute.

一个Scheme就包含了一套targets(这些targets之间可能有依赖关系),一个configuration,一套待执行的tests。

这5者的关系,举个可能不恰当的例子, Xcode Workspace就如同工厂,Xcode Project如同车间,每一个车间能够独立于工厂来生产产品(project可独立于workspace存在),可是各个车间组合起来就须要工厂来组织(若是用了cocopods,就须要用workspace)。Xcode Target是一条条的流水线,一条流水线上面只生产一种产品。Build Settings是生产产品的秘方,若是是生产汽水,Build Settings就是其中各个原料的配方。Xcode Scheme是生产方案,包含了流水线生产,秘方,还包含生产完成以后的质检(test)。

2. 来建立一个xcconfig文件

而后建立好了这个文件,咱们在project里面设置一下。

在这些地方把配置文件换成咱们刚刚新建的文件。

接下来就要编写咱们的xcconfig文件了。这个文件里面能够写的东西挺多的。细心的同窗就会发现,其实咱们一直使用的cocopods就是用这个文件来配置编译参数的。咱们随便看一个简单的cocopods的xcconfig文件,就是下图这样子:

GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/Forms"
OTHER_CFLAGS = $(inherited) -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/Forms"
OTHER_LDFLAGS = $(inherited) -ObjC -l"Forms"
PODS_ROOT = ${SRCROOT}/Pods复制代码

咱们因为须要配置网络环境,那能够这样写

//网络请求baseurl
REQUESTBASE_URL = @"http:\\/\\/10.20.100.1"复制代码

固然也能够写成cocopods那样

GCC_PREPROCESSOR_DEFINITIONS = $(inherited) WEBSERVICE_URL='$(REQUESTBASE_URL)' MESSAGE_SYSTEM_URL='$(MESSAGE_SYSTEM_URL)'复制代码

这里利用了一个GCC_PREPROCESSOR_DEFINITIONS编译参数。

Space-separated list of option specifications. Specifies preprocessor macros in the form foo (for a simple #define) or foo=1 (for a value definition). This list is passed to the compiler through the gcc -D option when compiling precompiled headers and implementation files.

GCC_PREPROCESSOR_DEFINITIONS 是 GCC 预编译头参数,一般咱们能够在 Project 文件下的 Build Settings 对预编译宏定义进行默认赋值。

它就是在Build Settings里面的 Apple LLVM 7.X - Preprocessing - Preprocessor Macros 这里。

Preprocessor Macros 实际上是按照 Configuration 选项进行默认配置的, 它是能够根据不一样的环境预先制定不一样定义的宏,或者为不一样环境下的相同变量定义不一样的值。

xcconfig 咱们能够写入不一样的 Configuration 选项配置不一样的文件。每个 xcconfig 能够配置 Build Settings 里的属性值, 其实实质就是经过 xcconfig 去修改 GCC_PREPROCESSOR_DEFINITIONS 的值,这样咱们就能够作到动态配置环境的需求了。

最后还须要提的一点是,这个配置文件的level的问题。如今本地有这么多配置,到底哪个最终生效呢?打开Build 里面的level,咱们来看一个例子。

咱们目前能够看到有5个配置,他们是有优先级的。优先级是从左往右,依次下降的。Resolved = target-level > project-level > 自定义配置文件 > iOS 默认配置。左边第一列永远显示的是当前生效的最终配置结果。

知道了这个优先级以后,咱们能够更加灵活的配置咱们的app了。

最后关于xcconfig配置,基本使用就这些了。可是这里面的学问不只仅这些。

还能利用xcconfig动态配置Build Settings里面的不少参数。这其实相似于cocopods的作法。可是有一个大神的作法很优雅。值得你们感兴趣的人去学习学习。iOS大神Justin Spahr-Summers的开源库xcconfigs提供了一个类权威的模板, 这是一个很好的学习使用xcconfig的库,强烈推荐。

最后这里有一个Demo,配置了Cocopods,配置了xcconfig文件,还有Build Configuration的,你们能够看看,请多多指教,Demo

三.利用Targets来配置多环境

配置一个多环境其实一个Scheme和xcconfig已经彻底够用了,为何还要有这个第三点呢?虽然说仅仅为了配置一个多环境这点“小事”,可是利用多个Targets也能实现需求,只不过有点“兴师动众”了。

关于构建Targets这个技术,我也是在2年前的公司实践过。当时的需求是作一个OEM的产品。本身公司有主要产品,也帮其余公司作OEM。一说到OEM,你们应该就知道Targets用到这里的妙用了。利用Targets能够瞬间大批量产生大量的app。

2013年巧哥也发过关于Targets的文章,猿题库iOS客户端的技术细节(一):使用多target来构建大量类似App,我原来公司在2014年也实现了这种功能。

仅仅只用一套代码,就能够生产出7个app。7个app的证书都是不一样的,配置也都不一样,可是代码只须要维护一套代码,就能够完成维护7个app的目标。

下面咱们来看看怎么新建Targets,有2种方法。

一种方法是彻底新建一个Targets,另一种方法是复制原有的Targets。

其实第一种方法创建出Targets,以后看你需求是怎么样的。若是也想是作OEM这种,能够把新建出来的project删掉,本地仍是维护一套代码,而后在新建的Targets 的Build Phases里面去把本地现有代码加上,参数本身能够随意配置。这样也是一套代码维护多个app。

第二种方法就是复制一个原有的Targets,这种作法只用本身去改参数就能够了。

再来讲说Targets的参数。

因为咱们新建了Targets,至关于新建了一个app了。因此里面的全部的文件所有均可以更改。包括info.plist,源码引用,Build Settings……全部参数均可以改,这样就不只仅局限于修改Scheme和xcconfig,因此以前说仅仅配置一个多环境用Targets有点兴师动众,可是它确实能完成目的。根据第二章里面咱们也提到了,Targets至关于流水线,仅次于Project的地位,能够想象,有了Targets,咱们没有什么不能修改的。

PS.最后关于Targets还有一点想说的,若是你们有多个app,而且这几个app之间有超过80%的代码都是彻底同样的,或者说仅仅只是个别界面显示不一样,逻辑都彻底相同,建议你们用Targets来作,这样只须要维护一套代码就能够了。维护多套相同的代码,实在太没有效率了。一个bug须要在多套代码上面来回改动,费时费力。

这时候可能有人会问了,若是维护一套代码,之后这些app若是需求有不一样怎么办??好比要进入不一样界面,跳转不一样界面,页面也显示不一样怎么办??这个问题其实很简单。在Targets里面的Compile Sources里面是能够给每一个不一样的Targets添加不一样的编译代码的。只须要在每一个不一样的Targets里面加入不一样界面的代码进行编译就能够了,在跳转的那个界面加上宏,来控制不一样的app跳转到相应界面。这样本地仍是维护的一套代码,只不过每一个Targets编译的代码就是这套代码的子集了。这样维护起来仍是很方便。也实现了不一样app不一样界面,不一样需求了。

最后

其实这篇文章的需求源自于上篇Jenkins自动化持续集成,有一个需求是能打不一样环境的包。以前没有Jenkins的时候就改改URL运行一遍就好,虽然说作法不够优雅,可是也不麻烦。如今想持续集成,只好把环境都分好,参数配置正确,这样Jenkins能够一次性多个环境的包一块儿打。真正作到多环境的持续集成。

最后就能够打出不一样环境的包了。请你们多多指教。

相关文章
相关标签/搜索