前言
很多时候,工程管理是一个很实际的技能,我们在实际开发中普遍有很多环境:测试环境、开发环境、生产环境等。还有可能遇到需要创建两个很多内容相同,部分功能不同的工程。
如果需要切换环境发版本的话,你可以手动注册修改代码实现(如果你喜欢这样的话,也就不需要继续看本文了),不是说这样很low,而是当不同环境的差异比较多的话,这种手动管理工程版本的方法笨拙而且容易出错。
创建两个很多内容相同,部分功能不同的工程,你可以选择拷贝,修改,保存,成为两个工程。当然还有另一种优雅的姿势。
这些问题都可以通过设置多个 Targets 来解决。
其它知识补充
workspace 、Project、target、 Scheme 的关系和简介
workspace 是Xcode的一种文件,用来管理工程和里面的文件,一个workspace可以包含若干个工程
project 里面包含了所有的源文件,资源文件和构建一个或者多个product的信息。project利用他们去编译我们所需的product,也帮我们组织它们之间的关系。一个project可以包含一个或者多个target。
target 每个target都继承了project的默认设置,每个target可以通过重新设置target的编译选项来定义自己的特殊编译选项。一个target对应于一个product(产品)。
scheme 定义了编译集合中的若干target,编译时的一些设置以及要执行的测试集合。我们可以定义多个scheme对应一个target。
关于Target
相信很多人都注意到XCode中, 有个Target的概念.那么这个Target到底是什么呢?
Apple的人是这样说的:“ Targets that define the products to build. A target organizes the files and instructions needed to build a product into a sequence of build actions that can be taken.”
简单的理解的话, 可以认为一个target对应一个新的product(基于同一份代码的情况下). 但都一份代码了, 弄个新product做什么呢 ?
其实这不是单纯的瞎折腾, 虽然代码是同一份, 但编译设置(比如编译条件), 以及包含的资源文件却可以有很大的差别. 于是即使同一份代码, 产出的product也可能大不相同.
Targets之间, 什么相同, 什么不同!
既然是利用同一份代码产出不同的product, 那么到底不同Target之间存在着什么样的差异呢?
要解释这个问题, 我们就要来看看一个Target指定了哪些内容.
从XCode左侧的列表中, 我们可以看到一个Target包含了Copy Bundle Resources, Compile Sources, Link Binary With Libraries. 其中
Copy Bundle Resources 是指生成的product的.app内将包含哪些资源文件
Compile Sources 是指将有哪些源代码被编译
-
Link Binary With Libraries 是指编译过程中会引用哪些库文件
Paste_Image.png
通过Copy Bundle Resources中内容的不同设置, 我们可以让不同的product包含不同的资源, 包括程序的主图标等, 而不是把XCode的工程中列出的资源一股脑的包含进去。
而这还不是一个target所指定的全部内容. 每个target可以使用一个独立, 不同的Info.plist文件。
我们都知道, 这个Info.plist文件内定义了一个iPhone项目的很多关键性内容, 比如程序名称, 最终生成product的全局唯一id等等。
而且不同的target还可以定义完整的差异化的编译设置, 从简单的调整优化选项, 到增加条件编译所使用的编译条件, 以至于所使用的base SDK都可以差异化指定.
两种添加Targets的方式
拷贝原有的Target
第一步:项目里面创建了多个target(分别是:生产环境、测试环境、开发环境),每个target对应一个环境,并配置不同的info.plist文件,这样做的好处是不用开发人员每次都要去手动开启/注释某些代码去发布,而是先配置好,到时候直接切换target就可以打包上线了切换一下target,运行一下,就是一个新项目。
弹出来的的页面是:一般选择中间那个选项 Duplicate only,如果有pad,则选择最后一个
第二步:
第三步:(这里不要忘了把你原来的Info.plist文件也勾选对了)
第四五六七步:
通过在不同的Targets 预定义宏(Build Setting–>Preprocessor Macros)区分不同的工程环境
这个宏是一个全局宏,在所有/整个工程的代码中都是有效的,我们可以在这里添加上环境与处理的宏(例如:WD_Environment_Mode,这个是随便自己取的)
#然后在.pch文件中
/**
WD_Environment_Mode
0:生产环境
1:测试环境
2:开发环境
*/
#ifdef WD_Environment_Mode
#if WD_Environment_Mode == 0
#define WDAPIServerUrl @""
#define JSPatchAppKey @""
#elif WD_Environment_Mode == 1
#define WDAPIServerUrl @""
#define JSPatchAppKey @""
#elif WD_Environment_Mode == 2
#defineWDAPIServerUrl @""
#defineJSPatchAppKey @""
#else
#warning"未匹配环境"
#endif
***************************************
更多关于 预定义宏的使用
而 Xcode 在產生新的 project 時,會自動在 Debug scheme 裡面加入 DEBUG=1 這個 Preprocessor Macro.
因此可以使用 DEBUG 這個 preprocessor macro 來區分 debug 和 release mode.
ViewController.m
...
#ifdef DEBUG
[self.hintLabel setText:@"Debug mode"];
#else
[self.hintLabel setText:@"Release mode"];
#endif
...
如果你不喜欢、不习惯这样的代码写法 可以在 PrefixHeader.pch 裡面加入以下的片段:
PrefixHeader.pch
#ifdef DEBUG
#define debug_only YES
#else
#define debug_only NO
#endif
ViewController.m
...
if(debug_only){
[self.hintLabel setText:@"Debug mode"];
} else {
[self.hintLabel setText:@"Release mode"];
}
...
就可以在所有的 source code 都直接用 if(debug_only){ ... } 來將 debug 時才會用到的程式片段包起來了。这是你最熟悉的方式是吧.
****************************************
总结一下,上面这种复制 target的方式比较适合不同的环境需要用到的变量值不同,也就是通过不同的 Target里面的 全局宏的值来做判断依据的,但是整套工程还是只有一套代码,相当于做了条件编译。
生成一个新的target,一定会与原target有区别,这里可以定义预编译宏,来区分两个版本的不同代码,预编译宏可以在Build Settings中Preprocessor Macros定义,比如在我们新建的target B中定义预编译宏MACRO,然后在代码中通过
#if defined (MACRO)
//target B需要执行的代码
#else
//target A需要执行的代码
#endif
来区分,并且同时又可以通过新Targets 来实现APP的 Logo,启动图,App名称的个性化定制!
创建全新的target
有的时候,我们创建两个很多内容相同,部分功能不同的工程,你可以选择拷贝,修改,保存,成为两个工程。当然你也可以利用 多个 Target实现。
但是有时候,两个版本里面的资源是冲突的,不能同时导入到一个target,上面我们通过复制 Target实现的其实是条件编译,并不是真正的新Target。下面的方式就能解决上面提到的冲突的问题。
最赞的是,这种方式即可以解决导入资源冲突的问题,又可以把公用的部分拿出来,供两个Target使用,真正做到了:只修改或者创建不同的功能,公用相同的功能。
会出现一个弹框,向下滚动,选择Single View Application-->Next-->Produce Name -->Finish
但是这样生成出的Target几乎是空的. Copy Bundle Resources, Compile Sources, Link Binary With Libraries里面都没有任何内容. 编译设置也是完全原始的状态。等于是一个新的小工程。
下面就在一个复杂成熟系统中新增target的真实操作为案例详述实际中的操作
需求场景:当时是因为需要用海康的新版SDK做一个APP,但是这个APP的其余部分都跟以前的APP是一样的,于是就想到了新增target,通过共用公共模块,不同target 实现不同的视频监控功能。
基础知识补充
- 【1】Xcode左侧的文件不一定都是实际目录下存在的,有的只是别的目录位置下的文件的引用。
- 【2】Xcode左侧的文件不一定属于看上去的目录,没有出现的文件也一定不能被该 Target使用。
- 【3】修改Xcode左侧的实际存在的文件/文件夹位置会直接修改文件的实际物理位置。
- 【4】如果一个工程下(即 project的文件夹下)已经存在了某一个文件 .h 或者.m时,当该文件再次被拖进工程中时,就算我们勾选了“Copy items if needed”该文件也看似新增了并出现在了Xcode的左侧导航中,但是这个“新增”的文件却是一个引用文件,并非重新创建了一份。
-
【5】工程文件中添加生产新文件的方式只有两种:
- A.原工程中没有该文件并且在拖进工程中时 勾选了“Copy items if needed”。
- B. 手动复制并生成在实际物理位置下,再拖入并引用至工程中。
步骤:
一、可以梳理工程中的公用文件到一个公共文件夹中,注意这个时候因为移动了文件的实际物理位置,我们可以通过先移除对文件夹的引用 Remove References,注意不是 Move to Trash
文件夹的实际位置从A移动到了B
当我们整理好公用部分的文件后再次拖入工程后,即便我们如下图那样选择了Add to targets 到所有的 targets后:
公共文件夹中的文件还是没有被引用到任何的 target中
二、给新 Target 的 Compile Sources 、Link Binary With Libraries 、Copy Bundle Resources 中添加需要的文件引用。
- Compile Sources中添加需要的所有 .m/.mm 文件,不需要添加进 .h 文件,这个后面会说。
- Link Binary With Libraries 中添加的是需要的所有静态库、系统类库。
-
Copy Bundle Resources 中添加所有需要的图片或者其他资源文件 包括:xib、 storyBorad。
基本上上面三个位置就是一个 Target 的所有能引用到的资源设置。
并设置好特殊类的编译设置:
三、设置新 Target 的 info.plist 文件和 .pch文件路径,注意.pch文件路径 就是该文件在工程目录中的相对实际物理路径
比如上面的这张图中:.pch的文件路径就是 Modular/GlobalConfig/xxxxx.pch
四、注意事项
1、 两个不同 Target中可以存在相同名称的 .h 和 .m 文件,不会出现重名的冲突。
2、关于.h文件,有一个很有意思的事情就是,我们在引用设置的时候不用管任何 .h 文件,一般第三方中会有大量的 .h文件,我们都不需要管,在新的 Target 中如果没有对应名称的 .h 文件,编译器就会从整个工程目录中搜索,如果在其他的Target 中找到对应的 .h文件就会直接引用,如果在当前 Target中搜索到对应名称的 .h文件就会引用当前Target内的 .h文件,即便别的Target中也有相同名称的 .h 文件也不会被引用。
3、当然你也可以不整理出公用的公共部分文件,这样就直接 new一个 Target ,完成 Bouild Phases的资源引用设置后,设置好 info.plist 文件和 .pch文件路径就完成了多 Target 的工程设置了。
本文参考文章
手把手教你给一个iOS app配置多个环境变量
使用 Preprocessor Macros 區分 release 和 debug 版本
如何在iOS项目中创建多个target
iOS新建target,使两个不同项目共用某一模块