游戲助手技術(shù)細節(jié):結(jié)合Shell Script快速建立大量相似iOS App

游戲助手技術(shù)細節(jié):結(jié)合Shell Script快速建立大量相似iOS App

背景

游戲助手項目是由一系列的游戲助手App組成,需要在現(xiàn)有基礎功能上,通過換皮膚、渠道識別以及新增功能,快速制作出小差異化的App,例如:爐石傳說助手、迷你西游助手,大話西游2助手等。

游戲助手系列

如何復制?

目前,有兩種可行方式:Targets(編譯目標)與Subprojects(子工程)

  • Targets - 同一項目工程里,通過復制多個target,利用target與scheme的配合編譯不同的資源文件,得到多個App;
  • Subprojects - 把主框架獨立成庫項目,再復制出多個子工程得到多個App。

下面,先從Targets方式說起,分別介紹一下兩個方式在我們項目中的具體實踐,并講述為什么我們放棄使用targets的方式,以及使用子項目方式都有哪些難點和關(guān)鍵點。

Targets方式

話不多說,先直接上圖給大家看看:

工程結(jié)構(gòu)

雖然xcode的文件組是虛擬的,但真實文件結(jié)構(gòu)也差不多,就不截圖上來了,下面直入實現(xiàn)細節(jié)。

實現(xiàn)細節(jié):

  1. 每個target指定不同的Info.plist文件;
  2. Build Phases的Copy Bundle Resources中僅添加對應資料夾的資源;
  3. Build Settings -> Preprocessing添加Preprocessing Macros預處理宏常量以區(qū)分各App的其它實現(xiàn)細節(jié);
  4. 復制同名帶_TS的為測試服數(shù)據(jù)target,區(qū)別在于bundle id添加測試服標識,以及Build Settings -> Custom Compiler Flags設置預編譯常量表明為測試服數(shù)據(jù)源;
  5. 預編譯常量:每個資料夾下獨立的AppBuilder.h(例如開放平臺id與統(tǒng)計id等);
  6. 服務端通過渠道標識區(qū)分來自哪個助手App;
  7. 指定target對應的scheme進行編譯,最終得到不同App。

使用Run Script

  1. Run Script腳本:放置bundle id等基礎信息,每次跑都修改Info.plist,而不是直接修改Info.plist;
  2. Run Script處理AppBuilder.h的使用,通過拷貝至基礎代碼替換文件的方式實現(xiàn)xcode唯一引用,而非直接引用資料夾下的。
腳本流程

復制助手的步驟:

  1. 右擊某個助手target,Duplicate(通過復制新建)這個target;
  2. 移除target內(nèi)上一個助手的資源,例如幾百個上個助手的皮膚圖片,一些不可重用有代碼等,但注意不要碰到基礎代碼內(nèi)的東西;
  3. 建立資源資料夾,準備Info.plist,AppBuilder.h,以及相關(guān)資源;
  4. 資源文件僅添加進xcode的這個新建target;
  5. 修改AppBuilder.h,Run Script的內(nèi)容;
  6. 修改xcode中新target的Product Name;
  7. 使用新的Info.plist為target的Info.plist
  8. 修改target內(nèi)的其它預編譯常量
  9. 通過對這個新的target 進行duplicate,修改類似相關(guān)的點得到測試服target

以上步驟缺一不可,當然,還是有進一步整理空間的,但主要問題不在這,請繼續(xù)往下看。

Targets的優(yōu)缺點:

優(yōu)點是工程結(jié)構(gòu)簡單,清晰,統(tǒng)一,另外唐巧《使用多target來構(gòu)建大量相似App》對此進行了很好的詮釋,最初我們的項目工程也是這種方式,但當助手數(shù)量增加后,但缺點也越來越明顯:

  • 工程文件(xcodeproj)日益增大,如上面的Copy Bundle Resource就有1700+資源文件,compile的文件就300+,每一個資源文件的引用就是1行記錄,14個助手App,就有28個target,共28x2000 = 56000行記錄,單個文件就20MB;
  • 因為上一條,導致xcode處理效率下降,甚至卡死,哪怕只修改target內(nèi)的一個字母,2012年i5 MBA直接卡頓30秒以上;
  • 在協(xié)作與版本管理上,會造成多個人多次反復修改同一項目核心文件project.pbxproj,出現(xiàn)沖突機率很大,而且該文件不適合人工修改,一旦沖突出現(xiàn),如果修改得多,他人根本就無從下手,xcode也無法打開,只好無奈revert了。

在SNS工具上也為此向大牛唐巧請教過,他也無奈表示目前還沒什么辦法解決,于是我們決定用子項目的方式把target分出來。

子項目方式:利用腳本批處理復制

實現(xiàn)計劃

  1. 確定基礎功能明確,確立框架,最終基礎庫由Foundation與UI兩庫組成,以.a文件形式提供;
  2. 代碼重構(gòu),把注入式的配置文件AppBuilder.h、預編譯常量全部分離,脫出基礎庫,基礎庫不再預編譯進任何助手信息;
  3. Run Script改造,不再耦合任何助手信息,轉(zhuǎn)為讀取另外配置文件,并獨立成文件;
  4. 精簡target,不再儲存資源文件引用以外的信息
  5. 所有信息,包括需要預編譯信息,統(tǒng)一由運行期設置
  6. 建立Template項目,使用xcode workspace組織各子項目
  7. 編寫腳本,從Template生成新項目,目的把上面繁瑣的復制步驟去人工化,減負并減少出錯機率

執(zhí)行難點

大量的預編譯常量使用

因為AppBuilder.h使用預編譯常量記錄信息,在基礎庫里散落各地,需要逐一整理出來,并用運行時的單例來取代它們。這部分沒什么辦法,只能慢慢挑魚骨頭。

原代碼:

#if !kOpenPlatformEnabled
    self.shareButton.hidden = YES;
    //codes...
#else
    self.shareButton.hidden = NO;
    //codes else...
#endif

修改為

if (![BuildInfo shareInstance].openPlatformEnabled]){
    self.shareButton.hidden = YES;
    //codes...
} else {
    self.shareButton.hidden = NO;
    //codes else...
}

因為#define常量在編譯期就固定值,不包含常量類型信息,xcode不能輔助識別和Refactor,不利于維護和debug,所以常量定義盡量使用const而非#define。

// File.h
extern NSString *const MyKey;

// File.m
NSString *const MyKey = @"MyKey";

預編譯 vs 運行時

預編譯可以選擇性的編譯代碼或打包資源,例如正式包不會包含任何測試服信息,即使逆向工程也無法從中找到任何信息,在一定程序上起到數(shù)據(jù)保護作用。

#if BUILD_FOR_ONLINE_APP
    return @"https://myonlinehost.163.com";
#else
    return @"http://123.123.123.123/";
#endif

project.pbxproj文件

利用子工程的方式與target方式一樣需要進行項目配置,但最大的問題還在于xcode工程文件本身的組織方式,

  • xcode文件的組是虛擬組,文件引用并非掃描當下目錄文件,不關(guān)心實體文件路徑只關(guān)心文件名,并且隱藏了具體路徑細節(jié);
  • 通過Scheme指定Target進行編譯,target參數(shù)眾多,人工復制修改需要好記性與細心,是個繡花活。
上圖:需要把左邊的模板修改成為右邊的樣子

.xcodeproj是一個包,顯示包內(nèi)容后會發(fā)現(xiàn)最核心的是project.pbxproj文件,這個文件其實是一個類似JSON形式的文本,但并非JSON,我們希望從中找出規(guī)律,讓腳本在復制的時候自動修改對應的內(nèi)容。

pbxproj文件

文件內(nèi)容非常龐大,但仔細觀察,還是能發(fā)現(xiàn)一些規(guī)律的,例如:

/* Begin PBXBuildFile section */
    94EDEA9C1A13500E00AAEA5F /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 94EDEA9B1A13500E00AAEA5F /* AppDelegate.m */; };
    ....
/* End PBXBuildFile section */

類似注釋號的/* Begin PBXBuildFile section */明確了需要Build的文件;而類似94EDEA9B1A13500E00AAEA5F為索引key,xcode通過key來檢索所以信息點。

當然,我們并不需要太過關(guān)心這些,畢竟這文件不是給人去修改的。

Hack掉project.pbxproj

  1. 首先,利用xcode做好一個Template工程,并把所有文件路徑設置為Relative to Group,這樣能簡化路徑搜尋;
relativetogroup
  1. 準備好要引用的代碼、資源、Info.plist、Framework等;
  2. 修改項目名字,scheme name,target name為好標記的字母,例如統(tǒng)一使用Sample為名字替換目標;
  3. 準備target內(nèi)其它需要的內(nèi)容;
  4. 關(guān)于AppDelegate.m,上面說了,xcode并不關(guān)心文件放哪里,所以你可以用A項目的AppDelegate.m放到B項目里,只要沒BUG,一樣能運行起來,所以最終的子項目只是借用了原AppDelegate.m,而不是每次都用新的文件,.h文件也如是。換句話說,xcode工程文件只是一個虛擬文件組織器,任何認為不需要獨立出來的文件都可以指向同一個,甚至是main.m文件!
  5. 讓工程正確運行起來,通過測試;

自動腳本Shell Script

當上面的操作準備好后,再打開project.pbxproj,會發(fā)現(xiàn)一切都如此清晰,只需要用shell來替換埋設好的關(guān)鍵字即可。

在Mac,用來替換文件字符串,并拷貝到目標目錄,可以用到sed命令:

sed -e "s/TemplateString/NewString/g" Template/template.xcodeproj/project.pbxproj > NewPath/NewNameFile/project.pbxproj

# 如果有多個要替換,可以多個集合多個替換字符串:
sed -e "s/TemplateString/NewString/g"  -e "s/TemplateString2/NewString2/g" Template/template.xcodeproj/project.pbxproj > NewPath/NewNameFile/project.pbxproj

我們的腳本必須能接受幾個基本的參數(shù),然后把參數(shù)轉(zhuǎn)成對應的信息:

例如名為create.sh,執(zhí)行時這樣:

./create.sh -n ZGMH -b com.abc.zgmh -c zgmh_channel -p 1004

腳本開頭可以這樣寫:

while [ "$1" != "" ]; do
case $1 in
    -n | --name )           shift
                            name=$1
                            ;;
    -b | -bundleid )        shift
                            bundleid=$1
                            ;;
    -c | --channel )        shift
                            channel=$1
                            ;;
    -p | --pushid )         shift
                            pushid=$1
                            ;;
    * )                     usage
                            exit 1
esac
shift
done    

這樣就能接受參數(shù)了

更多設置:settingVars.txt

在前文,我們看到在Run Script中進行了Info.plist修改,我們把Run Script單獨抽出來,并且用第三個文件來定義更多參數(shù)。

settingVars.txt:

# 配置信息 有空格必須用英文半角雙引號括起來
CHANNEL=sampleChannel           #渠道名與目錄
# 以下為info.plist內(nèi)對應的內(nèi)容

APP_BUNDLE_ID=appBundleId   #CFBundleIdentifier
CFBUNDLE_URL_NAME=zsUrlName #CFBundleURLName
CFBUNDLE_URL_SCHEMES=zsScmeme       #CFBundleURLSchemes
CFBUNDLE_DISPLAY_NAME=某某游戲助手        #CFBundleDisplayName

如何讀進去腳本里?

#read setting vars
varsContent=$(<settingVars.txt)
eval "$varsContent"

簡單粗暴到連自己都不相信[捂臉]。

如何讀寫plist文件?

/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName ${CFBUNDLE_DISPLAY_NAME}" ${INFO_PLIST_FILE_PATH}

通過PlistBuddy來設置和讀取plist,把接下來的字段補充完成即可,全部代碼就不貼出來了。

小結(jié)一下

  1. 在xcode中配置好一切,埋設標識符;
  2. 編寫Shell Script,用sed命令替換標識符并輸出成新的project.pbxproj文件,
  3. 復制Template下所有文件到新目錄;
  4. 編寫統(tǒng)一runscript,讀取新助手資料夾下的settingVars.txt內(nèi)容,修改Info.plist;
  5. 復制助手準備完成

以上的步驟只需要一次準備,以后的助手復制只需要一條命令:

./create.sh -n ZGMH -b com.abc.zgmh -c zgmh_channel -p 1004

項目使用了workspace來管理,只在需要的助手才放進去:

總結(jié)

善于運用Shell Script,能幫人自動化執(zhí)行一些重復勞動,結(jié)合xcode的run script,可以最大限度解放勞動力,提高效率減少人工錯誤率。上文內(nèi)容包括了大部分助手復制技術(shù)實現(xiàn)關(guān)鍵點,事實上我們還可以給run script傳參數(shù),用來生成不同的內(nèi)部測試包。下一步,還會把自動化打包部分腳本再完善,自動生成多個包,并進行簽名。

【完】

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,689評論 1 32
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時...
    歐辰_OSR閱讀 30,286評論 8 265
  • 用到的組件 1、通過CocoaPods安裝 2、第三方類庫安裝 3、第三方服務 友盟社會化分享組件 友盟用戶反饋 ...
    SunnyLeong閱讀 15,208評論 1 180
  • 小時候,我們家的墻上掛著一幅畫:一塊大石頭,石頭的一半被一朵朵盛開的牡丹遮掩著,石頭的旁邊是一條通往遠方的小徑。遠...
    一笑_c746閱讀 253評論 0 0
  • 禁漁期開始,市場的魚販們改賣冰凍和養(yǎng)殖的海鮮,價格也是更上一層樓的貴。我半輩子住在海邊,無魚不歡,尤其喜歡海魚,于...
    非恒道洪少京閱讀 487評論 3 7

友情鏈接更多精彩內(nèi)容