iOS - 建立大量相似App的另外一種選擇

weixin_33866037發表於2016-02-24

本篇文章主要針對iOS應用開發中, 針對需要建立許多相似的應用App提出一種新穎的解決方案。

關於如何建立大量相似的App,iOS大神@唐巧曾在他的博文《猿題庫iOS客戶端的技術細節(一):使用多target來構建大量相似App》提出了一種可行性非常高的解決方案。我本人也將該實現方案應用到了某二手車應用開發中, 通過建立多個target的方式建立了N個某某拍的應用。但是這種方案真的適用於所有場景麼? 除了使用這種方案能否有其它的方式去解決這個問題呢?

基於多Target的應用實踐

我剛開始接觸到開發多個相似App應用的需求的時候, 也採用了多個target的解決方案。主要做了以下工作:

  1. 建立多個Target (通過Duplicate行為)
  2. 為每一個Target指定LaunchImage和IconImage, LauchImage和IconImage由同一個image assert管理
  3. 為每一個Target指定了Info.PlistInfoPlist.strings, InfoPlist.strings的作用僅僅是為了指定CFBundleDisplayName
  4. 為每一個Target建立了一個用於配置應用特徵的JSON描述檔案, 用於對每個Target的特徵進行配置修改。
  5. 部署自動化打包平臺,防止有N個Target就手動打N次包。
28059-e8912fce55a878a8.png
配置Configuration的各個xcconfig

在上述工作中, 1、2、3均和配置項有關, 5與專案開發無關, 4是和具體的開發業務相關的。每一項的配置都沒有什麼技術深度和難度, 4的實現和具體需求相關, 對於極度相似的應用更多的行為是換膚和換key。

這裡稍微提以下關於InfoPlist.strings的指定, 每一個Target只能識別一個InfoPlist.strings, 而且還不能重新命名。需要為每一個Target建立一個物理資料夾, 然後在對應的資料夾下放置InfoPlist.strings防止命名衝突, 每一個InfoPlist.strings只能指定唯一識別的Target物件。(原理我還沒有找到, 找到我就更新下博文哈~)

差異性較大的Target處理

什麼? 差異性大你還放在一個工程裡? 架構就有問題。是的, 差異性較大的工程就應該拆分成不同的工程, 然後共享的程式碼通過framework以及靜態庫引用的方式抽離出去。<font color='orange'>但是, 時間是道坎!</font> 假如你時間很緊怎麼辦? 本文給出一種時間很緊時候的<font color='red'>臨時</font>解決方案(注意: 決必是臨時的, 時間是海綿, 需要去擠的!)

在時間非常緊的情況下, 可以通過拆分AppDelegate來實現(代價其實非常沉重, 會link好多無用的類)。拆分AppDelegate其實就要在main.m裡面賦值不同的AppDelegate即可實現。main函式中argv包含了app的名字, 可以通過該名字去鑑別載入的AppDelegate。

#import <UIKit/UIKit.h>

#import "STAppDelegate.h"
#import "STPAppDelegate.h"

int main(int argc, char * argv[])
{
    @autoreleasepool {
        char demoStr[] = "/stdemo.app"; // 檢查stdemo target
        char *p= strstr(*argv, demoStr);
        if(NULL != p){
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([STAppDelegate class]));
        }else{
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([STPAppDelegate class]));
        }
    }
}

PS: 切記, 臨時解決方案, 如需根治, <font style="font-size:1.5em">拆分工程</font>!

基於多Target實現的好處

  1. 直觀

    一目瞭然, 可以看到所有已建立的Target醒目的列在Build列表中。每一個Target都有對應的Tagret配置介面可以看到每一個專案配置圖示以及Info.plist對應資訊。

  2. 靈活性高

    可以根據專案需要Link需要的類, 根據Target來指定連結不同的類和資原始檔, 而不用一口氣全部都Link進來。

基於多Target遇到的坑

如果沒有遇到坑, 那就不會去重新尋找一個更好的解決方案了。基於多Target的方式去建立大量相似的App的坑主要提現在多人協作上。

個人之前在實現多Target專案的時候遇到的問題不多, 但是隨著時間推移, 維護開發遇到了兩個比較明顯的問題:

  1. 類的Target指定遺漏

    在多個Target的環境下, 我們每新建一個類檔案都要給類檔案指定對應的Target, 如果不小心忘記指定對應的Target, 則會會在編譯階段報錯。

    28059-f6d8c10245498373.png
    配置Configuration的各個xcconfig
  2. 配置檔案描述龐大, 難以修改

    多個Target會導致專案的pbxproj臃腫, 因為pbxproj檔案維護了專案的所有檔案id和group層級關係, 多一個target就幾乎多了一倍的描述資訊, 可想而知, 這個pbxporj檔案是有多龐大。

    光檔案龐大頂多引起Xcode專案的配置檔案載入慢, 但是遇到衝突的時候可就頭疼了, 幾萬行的描述檔案。

    28059-0ff2e8c33dd97a35.png
    配置Configuration的各個xcconfig
  3. 配置檔案修改不同步

配置檔案修改不同步是基於已建立N個Target的前提下, 因為專案的推進, 需要對每一個專案檔案進行固定的修改, 但是存在修改遺漏的情況。

對於這種場景, 有一種比較好的方案是自己動手寫指令碼來替換編譯配置項, 保證每一個Target的配置專案均被替換。Mac開發工具中自帶的PlistBuddy在處理配置專案替換上絕對是個神器。

重新思考

雖然在專案中遇到了不少坑,但是解決這些坑並不需要大量的時間(那是因為時間被打散了, 組合起來估計也不少了),所以我個人並沒有去重新思考怎麼去解決遺漏Target編譯報錯以及專案配置檔案不斷衝突的問題。

觸發我重新思考是一次機緣, 經過花瓣網某iOS研發高手(我不知道他名字哇)提點, 他問我基於Cocoapods能否有更好的辦法去建立大量相似的App。基於Cocoapods本身就是基於Hook, Hook本身就是動態修改專案配置項, 換言之, <font color='red'>能否通過動態修改Target的專案配置項去建立大量相似的App呢</font>?

回到文章前面的基於多Target的應用實踐的5個步驟, 逐一用替換專案的配置檔案(pbcproj)的方式去重新審視。

  1. 不需要建立多個Target, 只維護一個Target
  2. 主要是icon和launch image的修改, 有兩種方案:
    • 在image.assert預先放置多個不同名字的資源, 通過修改pbxproj來指定不同的圖片資源
    • 所有的icon和launch image都是用相同名字, 通過指令碼動態替換image.assert中的資原始檔(推薦)
  3. 主要針對info.plist和InfoPlist.strings的修改, InfoPlist.string可以通過sed命令去動態替換, info.plist也可以採取兩種方案來實現:
    • 預先防止多個Info.plist檔案, 通過修改pbxcproj來指定不同的info.plist檔案
    • target永遠指定一個Info.plist, 通過指令碼動態替換修改Info.plist(推薦)
  4. 通過JSON描述特性的檔案可以單獨防止在工程裡, 通過指令碼拷貝替換, 也可以利用cocoapods-keys等工具進行外部注入
  5. 前面的4個步驟都是依賴於基本動態替換, 自動化構建平臺通過將指定Target的方式, 修改為在編譯器執行對應的任務指令碼即可完成。

進一步優化

重新思考<font color='black'>通過外部修改配置專案和資原始檔的方式來實現多個類似應用功能</font>, 省去了維護多個target產生的衝突和配置過大的問題。但是, 外部指令碼本身也是一個實現成本, 這裡針對替換外部指令碼提出一個優化策略(不一定最優)。

  1. 維護每個專案的資料夾

    每一個專案就是指原來的每一個target, 資料夾可以保持和原先的target名字保持同名。該目錄資料夾不參與專案引用, 即不在pbxcproj檔案中被描述。該目錄資料夾純粹是提供給外部指令碼使用, 與邏輯工程保持獨立。

  2. 在第一步的資料夾中抽離變化專案到同一個JSON檔案中

    該json檔案中描述了所有需要替換的內容, 包含image.assert的替換規則以及info.plist替換規則等等。

  3. 在第一步的資料夾中抽離資原始檔

    在該資料夾中防止所有可變化的資原始檔, 包含.pnginfo.plist等等所有可變化差異的專案。

28059-9bc03e62b6ea0a8e.png
配置Configuration的各個xcconfig

在前面三步的基礎下, 主要是為了一個目的, 一行指令碼替換所有可變資訊。(實際上就是提前將變化項維護在獨立的資料夾中了)

## 動態變化 demo1 Target
./st_muti_target st_demo1/muti_target.json

## 動態變化 demo2 Target
./st_muti_target st_demo2/muti_target.json

想要st_muti_target.sh的原始碼? 這個自己寫吧。。每個專案都不一樣的。

總結

基於建立多個相似App的需求, 和本人實際在專案應用中遇到的坑, 提出了一種基於指令碼不斷替換配置專案和資原始檔的解決方案。該方案主要解決了多Target所帶來的配置檔案過大和容易衝突的問題, 但是同時又引入了指令碼的維護成本。本文也提供了一種降低指令碼使用成本和專案耦合的一種方案, 但是仍需要進一步優化, 並不是最終的解決方案版本。

多一種方案多一種選擇麼, 對於擅長書寫指令碼的童鞋們, 用這種方式做大量類似的App(換膚App)可能會是更好的一種選擇喔~

水平有限, 有錯誤之處或者有什麼地方沒有描述清楚, 請大家及時指出哇~

參考檔案:

  1. http://blog.devtang.com/blog/2013/10/17/the-tech-detail-of-ape-client-1/

相關文章