本文為投稿文章,作者:陳興Startry(博客)
關於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等等)。在多項目環境下, 配置文件修改配置項更加容易可控, 防止多處修改代碼或者使用腳本動態修改代碼。
文章的作用是給我本人備忘用的哈~ 水平有限, 有錯誤支持請大家及時指出哈~
轉載請注明出處哦~
參考文章:
http://pewpewthespells.com/blog/xcconfig_guide.html
http://www.jianshu.com/p/44c82630bd50