本文为投稿文章,作者:陈兴Startry( 博客 )
本篇文章主要讲述了如何使用 (Xcode配置文件xcconfig)去动态配置开发或者生产网络环境, 以及在多项目和运行中如何切换环境。
关于xcconfig文件, 目前在官方很难找到一篇专门的指南介绍, 但是国外有篇非官方指南 《The Unofficial Guide to xcconfig files》 详细的介绍了xcconfig。估计很多新入门的iOS开发对xcconfig文件都不是很熟悉, 但是大家可能都用过Cocoapods, 其实Cocoapods的项目配置管理很多都是依赖xcconfig文件去实现的。
Debug宏应该在哪里定义?
iOS系统本身就区分了Configurations选项让开发者去修改对应的开发环境配置, 但是因为很多开发者却又在同一个Configuration环境中自定义了开发环境配置的开发, 使得iOS系统本身的配置成为了摆设, 仅仅用于区分打包方式选项和证书配置。
网络环境切换 是每一个互联网App开发者都会频繁用到的功能, 那么大家都是用什么样的方式在切换环境的呢?我本人接触的项目中最多的就是在预编译头文件里面写一行宏定义, 然后根据宏定义去判断当前的环境。
最典型的例子 是在预编译头pch文件中添加一行代码 #define DEBUG 1 。然后通过这个 DEBUG 参数去判断当前环境是否处于开发网络或者生产网络环境。
使用 DEBUG 宏去判断判断开发环境还是生产环境没有任何问题, 关键的问题是我们在什么时候去定义这个宏和怎么去动态配置这个宏。
动态配置不同的网络开发环境
开发环境的切换在代码中最实用的还是宏定义, 那么我们怎么样才能够让宏定义动态可配置呢?
其中一种办法就是使用GCC预编译头参数 GCC_PREPROCESSOR_DEFINITIONS 。
通常我们可以在Project文件下的Build Settings对预编译宏定义进行默认赋值。在Xcode6下的路径为 Build Settings -> Apple LLVM 6.x Preprocessing -> Preprocessor Macros
想必大家看这个宏的名字已经知道它的作用了, 实际上就是和在pch头文件中添加宏定义没有太大的区别, 实际上还是有一些好处:
Xcode的Project的Build Settings是由一个plist文件进行描述的, plist本质上是一个XML配置文件, 通过外部的脚本比较容易去修改。
Preprocessor Macros可以按照Configuration选项进行默认配置, 也就是说可以根据不同的环境预先制定不同定义的宏
xcconfig配置Build Settings
Xcode Project的Build Settings属性有很多, 如果每一个属性都在配置项改过去比较麻烦, 而且容易忘记, 而且Build Settings用源码的打开可阅读性也不是很高, 这个时候, 我们可以使用xcconfig文件去配置Build Settings参数。
xcconifg支持可以根据不同的Configuration选项配置不同的文件。不同的xcconfig可以指定不同的Build Settings里的属性值, 这样子我们就可以通过项目xcconifg去修改 GCC_PREPROCESSOR_DEFINITIONS 的值了(最终目的就达到了)。
利用xcconfig配置Build Settings的方式比直接在项目Build Settings修改对应的属性值要优雅的多, 英国的iOS大神 Justin Spahr-Summers 书写的开源库 xcconfigs 提供了一个类权威的模板, 大家可以参考编写以及学习使用xcconfig。
Object-C下配置的支持
在项目中的Info类目下, 大家可以配置Configuration对应的选项的xcconfig, 通过xcconfig来配置Build Setting中的参数(见下图)。
配置Configuration的各个xcconfig
PS: 如果大家对Cocoapods比较熟悉的话, 你会发现其实Pods也是通过xcconfig文件去修改项目配置参数的。
Swift下配置的支持
这里区分Object-C和Swift没有太大的意义。只不过因为C语言使用一些非常不安全的预处理器指令能力,Swift则只使用预处理器指令的安全子集。因此预编译头参数在Swift并不会生效, 需要增加 OTHER_SWIFT_FLAGS 标记才能够将 Debug 作用于Swift的条件式判断。标记书写方式参考下方示例:
OTHER_SWIFT_FLAGS = -D DEBUG
动态修改配置文件
环境切换的标志位宏被提取到Build Setting中的 GCC_PROCESSOR_DEFINTIONS 有什么好处呢?
外部修改只需要修改工程的 project.pbxproj 即可对 GCC_PROCESSOR_DEFINTIONS 参数进行操作修改
可以通过xcconfig去配置参数, 而配有xcconfig的Configuration可以通过 xcodebuild 命令指定
可以避免将最基础的Debug和Release网络环境切换书写在代码中
自动化脚本支持(便于自动化构建)
一个优秀的iOS工程师一定会使用自动化构建应用去解放自己的打包时间。 《搭建自动化构建服务》 讲述了如何搭建一个自动化构建程序, 可以作为参考。
自动化构建的核心在于使用xcodebuild命令和各类脚本, 本文讲述2个场景:
场景1: 环境变量由宏定义并且书写在项目的预编译头文件中或者在预编译头文件引用的.h文件中;
通过脚本动态替换行, 可以采用sed命令来替换, 最典型的实例如下:
sed -i '' 's/^#define Debug 1/////#define Debug 1/' "./Demo/STSwitch.h"
场景2: 环境变量由宏定义但是配置 GCC_PREPROCESSOR_DEFINITIONS 编译选项中。
如果 GCC_PREPROCESSOR_DEFINITIONS 由xcconfig文件指定并配置对应的Configuration中, 直接通过 xcodebuild 命令指定- configuration 参数来选择。
如果 GCC_PREPROCESSOR_DEFINITIONS 需要在Build Settings中动态修改, 可以在Podfile中书写Hook代码或者用脚本解析配置文件进行动态修改。
Cocoapods下支持
如果大家对Cocoapods比较熟悉的话, 就会知道每次执行完 pod install 之后, Cocoapods都会对每一个工程的Configuration配置一个xcconfig文件。
默认情况下, 如果配置项已经存在了xcconfig文件, Cocoapods是不会将生产的xcconfig文件设置入配置项的。Cocoapods是通过xcconfig文件去修改外部链接依赖的, 因此如果没有正常替换配置文件, 有肯能会导致整个工程无法编译通过(缺少依赖库能通过才怪啦)。
解决方法
如果自己修改的xcconfig文件内容不多, 可以通过在Podfile中编写hook去实现修改对应的项目参数, 参考示例如下:
post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| if config.name == 'Debug' config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = 'Debug=1' end end end end
如果自己修改的xcconfig文件内容较多, 可以在自己的编写的xcconfig include Cocoapods生产的xcconfig文件的方式进行处理, 参考示例如下:
// 自定义的xcconfig (例如: st.debug) #include "../Pods.debug"
// 自定义的xcconfig (例如: st.release) #include "../Pods.release"
多项目环境下支持
多项目的环境配置往往是比较麻烦的, 比如有B、C、D三个子工程, A工程引用了B、C、D三个子工程。怎么把统一的环境变量怎么应用到A、B、C、D三个工程里呢?
做法1: 建立一个公有引用的项目(无论Pods还是手动), 所有的项目均引用这个公有的项目, 公有的项目暴露一个头文件里面定义了所有的环境变量。
做法2: 每一个项目均维护自己的初始值, 通过外部的脚本一次性修改所有项目的初始值保持统一。
做法3: 每一个项目维护自己的初始值, 通过上文描述的GCC编译属性或者xcconfig控制, 通过脚本或者Podfile控制每一个项目初始值
做法4: 每个项目的维护自己的初始值, 但是所有环境变量动态维护, 在主工程的AppDelegate中加载A项目的初始值并通过接口赋值给每一个子工程。(该方式宏定义智能作为初始值, 参考下文 动态切换配置 )
动态切换配置
文章前面所述均少了一个关键字 初始值 , 前面所添加的环境变量的方式都是在添加 初始环境变量常量 。
假设有一个运营或者测试需求, 需要能够用户自己去选择网络配置或者环境基础变量, 按照文章前面描述的方法, 是无法实现的。 因此, 上述的方式都只能提供一个初始默认值, 并无法在运行中去修改, 因为上述配置的方式都是通过 预编译 去实现的。
在App运行时切换环境, 那配置参数都不能简单的用宏或者常量来控制了, 需要讲环境配置参数存储在变量中, 通常是用 NSUserDefault 或者 Singleton 去维护环境变量集合。通过开发配置页面对维护的变量进行动态的修改。(建议在Debug模式下开启放置在系统的Setting界面下)
另外一种选择
除了通过宏初始化, 是否还有其它的读取配置文件的方式初始化呢?
我们可以维护一个单例去管理所有的初始值, 在iOS应用开发中用属性值去管理开发环境和生产环境(坏处很明显, 控制力度没有宏这么大)。
资源文件加载其实就是把参数写在资源文件中, 然后通过代码在AppDelegate启动的时候去加载初始值到全局维护的单例中, 然后在工程中到处使用单例的实例变量去判断环境。
Cocoapods-keys
通过配置加载环境配置环境变量是否能够做到工程依赖无关呢? 换言之就是在不同的安装目录或者不同的机器环境下配置不一样的环境变量, 让环境变量不与工程直接关联而与工程所在目录环境关联起来呢?
做过服务端开发的童鞋们应该熟悉有一种config配置的方式, 让config在外部注入, 而不是在开发工程中写死, 即使写死也只是个初始值。
Cocoapods提供了一款插件 Cocoapods-keys , 提供了外部注入键值对的功能, 通过Cocoapods-keys插件, 我们可以在工程中调用外部注入的键值对, 通过外部的键值对工程进行一定力度的控制。
Cocoapods-keys注入的键值对均存储在 ~/.cocoapods/keys 下, 是以yml的格式保存的, yml中描述了已经添加的键值对和对应项目路径。
总结
文章想表达的核心思想是将环境切换初始化提取到配置文件处, 方便外部脚本修改(例如Podfile、自己写的Bash Shell等等)。在多项目环境下, 配置文件修改配置项更加容易可控, 防止多处修改代码或者使用脚本动态修改代码。
文章的作用是给我本人备忘用的哈~ 水平有限, 有错误支持请大家及时指出哈~
转载请注明出处哦~