前言
本人今年主要在負責猿題庫iOS客戶端的開發,本文旨在通過分享猿題庫iOS客戶端開發過程中的技術細節,達到總結和交流的目的。
這是本技術分享系列文章的第一篇。本文涉及的技術細節是:採用多Target編譯方案來實現多個相似App的開發,以保證我們能夠快速地推出多個相似課程的客戶端。
問題描述
今年春節後,我們對外發布了應用“猿題庫-公務員考試行測”,接着我們就開始一個個發佈猿題庫系列課程應用。到現在半年多過去了,我們一共對外發布了8款應用(如下圖所示)。
這些課程,隨了“猿題庫-公務員考試申論”和其它課程不一樣之外,另外7個課程都有着相似,但是又不完全相同的功能和界面。
這些應用的相同點包括:
- 基本相同的註冊和登錄以及首頁邏輯和界面(只是背景圖片不一樣而已)。
- 相同的做題邏輯和界面。
- 基本相同的答題報告顯示界面。
- 基本相同的能力評估報告界面。
不同點主要包括:
- 應用圖標,啓動畫面,應用啓動後的首頁都不一樣。
- 有些課程(例如公務員考試和高考)是有目標考試的概念,不同的目標考試大綱是不一樣的。拿高考來舉例,北京的高考和上海的高考,就有着完全不一樣的考試大綱。高考的文科和理科,又有着完全不同的考試科目。
- 有些課程會有一些自定義的界面,例如高考的應用可以設置暱稱,有些課程的真題練習中是有推薦真題模塊的,而有些課程又沒有。
- 有些課程有掃描答題卡功能,有些課程有考前衝刺功能,有些課程有大題專項查看功能,而有些課程又沒有上述功能。另外還有一些微小細節,但是解決方法和類似,所以就不一一展開說明。
技術解決方案
我們的技術解決方案主要說來分4步:
- 通過抽取子項目,構建可複用的大模塊。
- 通過多Target編譯的方式,不同課程的在編譯時,採用不同的資源文件和源文件。
- 在第2步的基礎上,在項目中創建配置用的Config類,然後在不同Target各自的配置文件中設置不同的Config值。實現課程的差異化界面。
- 從不同的xib中加載界面。
抽取子項目
我們首先做的是抽取子項目,從“猿題庫司法考試客戶端”開始,我們將可以重用的模塊一一抽取出來,以git submodule的形式組織到項目中。這個抽取過程在開發完猿題庫司法考試客戶端之後,基本成型了。我們抽取的submodule主要分爲4部分:
- UI Common,涉及可複用的登錄界面,註冊界面,付費界面,NPS界面,意見反饋界面,關於界面,掃描答題卡界面。另外,我們將一些可複用的UI風格控件也抽取成了相應的靜態工廠方法,用於生成統一風格的按鈕、背景以及狀態欄等。
- Core Common,涉及可複用的底層模塊。包括網絡請求模塊,自己封裝的Core Text渲染引擎,緩存模塊,一些靜態util方法等。
- Lib Common,所有第三方的開源庫依賴,有部分代碼根據我們的需求做了修改和定製。
- Scan Common, 答題卡掃描識別算法模塊,實現核心的掃描算法。
以上只是粗粒度劃分,這些模塊化的子項目可能在以後被重用,例如Core Common完全就可以複用在任何其它項目中。
構造多個編譯Target
抽取完子項目以後,我們採用多target的方式,將不同課程中的同名資源文件打包進各自的Target中,最後所有課程在一個工程項目中,如下圖所示:
先簡單介紹一下Xcode中target的概念,蘋果在文檔中寫道:
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.”
在Xcode的一個項目中,可以允許建立多個編譯的target,每個target代表着最終編譯出來的一個App文件,在每個target中,可以添加不同的編譯源文件和資源文件。最終,通過我們在不同target之間,修改其 Copy
Bundle Resources
和 Compile
Sources
配置,使課程之間的差異性得到實現。我們具體的配置方案如下:
-
我們的每個課程的資源文件都具有相同的文件名,例如首頁背景都叫 HomeBackgroundBg.png ,由於每個課程背景不一樣,所以我們在工程中,每一個課程target下,通過修改
Copy Bundle Resources
,使其都配置有不同的(但是同名) HomeBackgroundBg.png 。這樣的好處是,在代碼邏輯層面,我們可以完全不用處理課程間資源文件的差異性問題。資源文件的差異性都是通過配置文件來保證的。 -
對於文案一類的差別,我們通過修改
Compile Sources
,使不同的課程有着不同的文案定義文件。通過這樣,我們使不同課程有了不同的文案。另外包括後臺網絡接口的差異性問題,統計項的差異性問題,也都是這樣處理的。
Config類
最後,我們使用Config類來完成交互和頁面UI組件差異性問題。拿能力評估報告頁面來說,不同的課程的頁面都有一些差異。我們在公共層的代碼中將這些邏輯全部實現,具體的UI在呈現時,通過讀取相關的Config類來決定具體如何展示。這樣,我們只需要在第2步提供的各個課程的差異性源文件中,完成Config類的配置即可。
從不同的xib中加載界面
有些時候,我們僅僅需要的是UI界面排列方式不一樣,其它交互邏輯完全一樣。對於這種需求,我們嘗試同一個view對應有多個xib,然後通過上一步的Config類的信息,來加載不同的xib界面。這樣所有的差異性都在不同的xib中解決了,對controller層可以完全透明。
下圖是我們報告頁面的xib界面,分爲:高考課程、有目標考試的課程、沒有目標考試的課程三種。由於這3個界面的後臺邏輯和交互邏輯都一樣,我們通過3個xib來實現它們之間差異性的部分。
以下是view加載對應的xib的代碼邏輯:
+ (IPadAbilityReportHeaderView *)loadFromNib:(IPadAbilityReportHeaderViewType)type {
NSString *nibFileName;
switch (type) {
case IPadAbilityReportHeaderViewTypeWithQuiz:
nibFileName = @"IPadAbilityReportHeaderViewWithQuiz";
break;
case IPadAbilityReportHeaderViewTypeWithoutQuiz:
nibFileName = @"IPadAbilityReportHeaderViewWithoutQuiz";
break;
case IPadAbilityReportHeaderViewTypeGaokao:
nibFileName = @"IPadAbilityReportHeaderViewInGaokao";
break;
default:
break;
}
NSArray *nibArray = [[NSBundle mainBundle] loadNibNamed:nibFileName owner:nil options:nil];
if (nibArray.count > 0) {
return [nibArray lastObject];
} else {
return nil;
}
}
總結
通過多target編譯方案,我們可以很方便的實現多個相似App的開發,以保證我們能夠快速地推出多個相似課程的客戶端。同時,由於在一個工程中,我們也可以方便地測試新的代碼邏輯在各個課程下是否正常。
該方案可以用來解決“維護大量邏輯相似但是又有細微不同的應用”的需求,希望本文能給業界同行一些幫助。