京東iOS客戶端組件管理實踐

AudreaWilhi 7年前發布 | 23K 次閱讀 iOS開發 移動開發

編者按:從去年開始,關于iOS組件化的討論和分享非常多,也形成了幾種比較成熟的方案。組件多了,它們的依賴關系、版本等的管理成為問題,但這方面的分享很少。京東iOS不但實施了組件化,還專門開發了一套組件管理系統。希望京東的實踐可以給大家一些參考思路。

前言

先大概交代下背景:京東的iOS客戶端從2011年2月發布至今已歷經6年+的時間,研發團隊也從最終的幾個人變成了N多人,業務的復雜度早已不可想象。

我個人認為一個超過了10人的團隊做組件化是合適的,也有必要。當然少于10個人也應該去思考一下應用框架該如何演變,組織的這件事。

目標

對于每家應用還得結合實際業務來考慮,畢竟技術最終也是為了業務而服務。京東iOS組件化的目的從業務層面來講主要是為了解決:多業務的并行集成,多部門的業務輸出。

從技術層面來講我們做組件化的目標是為了可以做到減緩代碼腐化過程,加快編譯時間,模塊分權管理,代碼規范,bug減少,獨立開發、調試、自動化編譯,集成等等工作(好像很厲害的樣子)。更近一步的講,我們需要一套自動化的系統來幫助我們完成所有的組件管理工作,讓開發人員能更專注于代碼層面,無需關心應用配置,渠道,以及如何集成等問題。

組件管理演進之路

任何事物都有一個演進的過程,就像羅馬不是一天建成的一樣。iOS這些年各種技術,花樣層出不窮,好多公司,好多大牛在iOS組件化方面分享出了好多寶貴的經驗,也讓我們少走了許多彎路。關于iOS組件化的做法每家公司都大同小異,真正需要我們去深挖的應該是怎么把組件有序的管理起來,這也是我們想和大家探討的內容,在討論組件管理內容之前我們先簡單說明下組件化實現的大概思路。

  • 代碼解耦
  • Cocoapods管理

代碼解耦

首先入手的工作就是代碼的解耦,這里其實沒有太多的技術含量,只要膽大心細,有計劃,一個工程總能被拆成一個個獨立的模塊。我們把每一個獨立的模塊就稱之為組件,相互之間不能通過硬編碼引用的方式進行調用。通信通過自定義的協議進行。

組件原則

  • 組件被定義為兩種類型的組件:基礎組件,業務組件。
  • 基礎組件可以被業務組件依賴,基礎組件不可依賴業務組件。
  • 業務組件不可依賴業務組件。

自定義協議

組件之間通信遵循一套自定義的協議通信,實現的方式網上有些開源的項目,我們綜合各家實現考慮,最后定出的一個方案:組件間的通信應該是輕量級的,調用完就走,不留痕跡,不需要維護通信數據。

大致實現如下:

(點擊放大圖像)

我們實現一個JDRouter的組件,用來實現組件與組件之間的通信。只暴露一個頭文件,兩個方法:輸入,輸出(宏規范)。

輸入(A調用B)

router://JDBClass/getString?name=Steven

輸出(B被A調用)

+(id)getDataWithString:(NSString *)name {
    NSString *str = [NSString stringWithFormat:@"HI, %@", name];
    return str;
}

輸入說明

通過JDRouter調用,類似于有這樣一個方法,完成a到b的通信

id g = [JDRouter openURL:@"router://JDBClass/getString?name=steven" arg:nil error:nil completion:nil];

輸出統一

輸入的方法統一了,輸出也得統一,沒有規矩不成方圓,但又不能通過說教的方式要求大家去提供輸出方法。如果有一個統一的辦法可以不需要協議注冊,協議管理的機制,直接寫一個類方法可以讓JDRouter通過URI里的內容可以映射過去就好辦了,我們使用宏替換,在JDRouter里提供一個輸出的規范,類似這樣:

#define JDROUTER_EXTERN_METHOD(m,i,p,c) + (id) routerHandle_##m##_##i:(NSDictionary*)arg callback:(Completion)callback

輸出規范統一,將上面的類方法變為宏替換輸出:

JDROUTER_EXTERN_METHOD(JDBClass, eat, getString, callback) {

    NSString *str = [NSString stringWithFormat:@"HI, %@", name];
    return str;

}

AppDelegate入口解耦

頁面容器,第三方SDK初始化工作需要在啟動時完成,我們通過hook AppDelegate,將入口所做的工作交給一個組件完成,在該組件中注冊其它需要在啟動時調用AppDelegate方法的組件,讓每個組件都可以擁有一類似didFinishLaunchingWithOptions方法。

  • AppDelegateModule組件實現hook AppDelegate

  • 入口組件

+ (void) load {
NSArray *modules = @[@"MainModule"];
NSString *url = @"router://AppDelegateModule/setDidFinishLaunchingModules";
[JDRouter openURL:url arg:modules error:nil completion:nil];

//
NSString *urlrun = @"router://AppDelegateModule/run";
[JDRouter openURL:urlrun arg:nil error:nil completion:nil];
}

在MainModule中實現AppDelegate的方法

static UIWindow *gWindow = nil;
static UIViewController *gTempViewController = nil;
+ (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    gWindow = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    [[[UIApplication sharedApplication] delegate] setWindow:gWindow];

    gTempViewController = [[UIViewController alloc] init];
    gTempViewController.view.backgroundColor = [UIColor redColor];
    gWindow.rootViewController = gTempViewController;
    [gWindow makeKeyAndVisible];
    return YES;
}

把實例方法改為類方法

將需要調用appdelegate方法的組件,在MainModule中注冊,這里的modules是個數據,可解決調用delegate方法的順序問題。

NSArray *modules = @[@"MainModule", @"需要調用的組件"];

Cocoapods管理

代碼解耦很簡單,只要遵循幾個原則即可,最根本的問題就是業務與業務之間不能有耦合,不然組件化這件事就沒有意義。我們通過Cocoapods把每一個組件都拆成獨立的pod庫。代碼庫管理選擇gitlab(開源,提供API,可二次開發),后續需要對每一個組件進行權限管理,比如有一些涉及到安全的組件只有安全組的開發人員具體源碼權限,其他人只能拿到二進制,再比如組件需要具有master的權限才可以進行發布,集成等工作。

關于Cocoapods,ruby,gitlab環境大家網上搜索一下。

準備工作

  • 通過Cocoapods搭建私有庫,創建相應的模版。
  • 不推薦Cocoapods編譯二進制文件,自己寫腳本編起來更靈活。
  • 自定義gem,完成podspec源碼二進制切換。
  • 二進制文件(組件編譯為靜態包)存儲到內部云。
  • 使用工具/腳本管理podfile。
  • 系統管理pod庫。
  • 工程結構
  • 通過Cocoapods搭的自定義庫,自定義模版。

每個同學拿到的組件都是一個相同結構的工程,所需要做的工作就是在相應的Pods/Developemnt Pods/組件/Classes下編碼,組件輸出類通過模版創建,可在相應的類里使用JDROUTER_EXTERN_METHOD提供接口。

iBiu組件管理系統

以上是行業中對iOS組件化的一個大體思路。如果我們完全手動做這些工作的話,成本會很大,Cocoapods配置說明查詢,組件版本依賴,統一集成等等。為了解決這些人為干預所引發的各種問題,我們研發了組件管理系統 iBiu

主App解耦工作和系統設計是同時進行的,所以最初系統只是為京東iOS主App所設計,在獨立出一些組件后,我們就在思考一件事,讓系統可管理京東其它的App。

去應用化

如果公司的所有應用全部都組件化,并且組件間統一協議通信,那么應用最終的輸出方式應該就像工廠加工一樣,加工過程就是組件組合過程,出廠時貼上標簽。聽起來很理想,事實上是可以做到的,從iBiu系統上線到目前一個月時間我們接入管理著三個應用包括主App,超過100個組件。

組件配置表

組件如何組合被抽象出一個組件配置表,記錄了不同應用的組件配置,對應到具體的組件責任人,版本,對接產品,測試以及開發,通過工具一鍵完成組件的發布與集成等工作。

(點擊放大圖像)

上圖解釋起來就是,假如有一個虛擬的App-A,在它下邊有包含了一系列的組件,我們可以通過“組件配置表”(配置表里記錄了組件的版本,是否為二進制,依賴等)對組件進行組合,最終輸出我們想要的App。

再將上邊的設計升級一下:

(點擊放大圖像)

讓應用可以包含應用(這里所說的應用是一個虛擬的應用,或者叫做Collection更合適)。Collection可以任意包含另外的Collection,同時可以拿到Collection下的組件,如上圖,App-A這個Collection最終是有另外三個Collection下的組件所組合而成。

系統設計

主要由三大塊內容構成:

  • 腳本(提供開發環境,如果pod管理,git管理,文件管理等)
  • iBiu工具(可視化)
  • iBiu Server(后臺管理,API)

(點擊放大圖像)

我們希望化繁為簡,最終開發同學只需要安裝一個可視化的工具就可以做到:組件注冊,組合,發布,集成等工作,但基礎工作還得一步步的來。

第一步:

通過pod命令創建相應的組件對應庫

pod lib create $lib_name --template-url=${TEMPLATE_URL}

第二步:

將創建組件的腳本封裝,我們希望把腳本放到iBiu Server這臺機器上,讓這個過程成為:開發者注冊->審核->自動生成組件->代碼提交Gitlab->通知開發者。

問題一個接一個的出現了:

坑:由于公司網絡原因,不同網段同學無法訪問一臺工作網絡環境中的機器。

坑:腳本創建組件這個過程依賴Xcode環境。

也就是說我們無法用一個Mac機器充當服務器,但又必須要Xcode環境。第一想法就是把iBiu Server部到線上環境,找一臺Mac機器把腳本放上去,讓iBiu Server訪問這臺Mac,但線上環境是完全返向不回來工作網絡的。

坑:換線下環境,部署iBiu Server,還是訪問不了工作網絡中的這臺Mac。

最后只能在iBiu Server這臺機器上部了套Jenkins,生成組件的腳本部在Mac這臺節點上,問題解決。(這塊一直是個不太理想的做法,為了解決問題也只能這么做了)

第三步:

統一用戶體系,ERP賬號與iBiu、Gitlab打通。

第四步:

修改Podfile

如果是手動引入組件的話,每一個組件對應的Podfile可能會是下圖這樣,該組件所有的開發者,對該組件引入修改都有可能造成沖突。

(點擊放大圖像)

如果可以通過Podfile讀取到“組件配置表”,而配置表又可以同步的話,能解決的問題就不只是沖突的問題了。

我們創建了一個biu gem,用來去處理配置表解析的工作,Podfile最終變成下圖的樣子,開發者無需手動修改它。

(點擊放大圖像)

所有工作都通過mekeup_pods這個方法完成。

第五步:

我們需要把每個組件在發布時都對應二進制文件輸出,有關xcodebuild打包二進制的腳本就不在這里描述了。

將二進制統一輸出為一個xxxx.framework,也方便查看

(點擊放大圖像)

腳本會把每次編譯的負責人信息寫到xxxx-umbrella.h文件中,方便問題跟蹤。

(點擊放大圖像)

第六步:

將所有shell腳本通過Packages打包,提供給所有開發者可以直接使用。

第七步:

iBiu可視化工具iBiu Server端開發,工具主體功能的開發時間并沒有花了太多的時間,主要時間還是花在了梳理整個管理流程上。

第八步:

合并iBiu腳本,可視化工具,Packages打包。

(點擊放大圖像)

組件管理實踐

系統及可視化工具

通過工具申請組件

(點擊放大圖像)

開發者選擇對應的App,輸入相應的組件名等信息,將注冊信息提交給iBiu Server,系統管理員會收到注冊郵件,在后臺完成審核。同時會系統郵件發給相應的開發者。

審核過程通過前面提到的,系統會自動完成創建pod庫,git庫,開發者權限分配等操作。

組件申請人即是該組件的負責人,擁用開發者權限分配,組件發布,集成,協議管理等權限。

后臺管理開發者權限分配:

(點擊放大圖像)

開發調試組合安裝

開發者收到組件審核郵件后,根據git地址拉取代碼,通過iBiu可視化工具打開組件工程,勾選要組合的組件進行安裝。

所勾選的組件可以根據當前用戶的權限分源碼與二進制兩種方式,是否依賴。

如果某用戶所組合的某個組件需要調試,但沒有源碼權限,可找該組件的負責人要求開通權限。

(點擊放大圖像)

生成組件配置表

(點擊放大圖像)

根據所勾選的組件,生成一張組件配置表,這時所組合安裝生成的App即是一個根據配置表生成的工程。

坑:配置表是脫離組件工程獨立存在的一個JSON描述文件,因為每個開發者對每一個組件所擁有的權限不一樣,如果把配置表隨工程代碼一并提交,很可能造成表沖突,安裝不了組件。解決辦法就是使擁用master權限的用戶可以將表內容同步至服務端,組件參與者下載同步表。然后在幾個關鍵點驗證表中組件對應權限,沒有源碼權限的提示用戶自動切為二進制方式。

組件安裝過程

(點擊放大圖像)

配置版本組合安裝

擁有組件master權限的用戶可以將相應組件發布,集成到某個應用下的某個版本。

我們的應用迭代周期一般為一個月左右,對應到不同的項目,不同的產品,研發及測試。通過iBiu系統可以做到并行版以本開發,測試,集成。

組件的發布,集成不受項目的時間限制,隨時可以發布。

App整體集成,組件的集成需要按項目流程進行,組件需要在App回歸測試時前兩天集成。點擊iBiu可視化工具右上角集成按扭,選擇組件版本,集成到某個應用的某個版本中。

每個應用的每個版本,在組件集成階段生成相應的組件配置表,超過這個時間,應用版本鎖定,組件將無法再集成到該應用版本中。

例如應用“京東”當前要發布AppStore的版本為6.1.3,發布時間7月19,倒推時間,灰度1周,集成測試為1周,那么所有該版本的組件必須在兩周前兩天完成集成。過期不候,只能按組件上一個穩定版本集成,如果有特殊情況需特殊對待。

JDRouter協議管理

每個業務組件都應該提供JDRouter接口,通過后臺管理這些接口,方便所有開發者查詢。

(點擊放大圖像)

持續集成改造

應用以前是可以持續集成的,iBiu系統組件管理后需要對持續集成系統做一些改造。在相應的節點機器上安裝iBiu安裝包,運行后自動安裝環境。

CI通過iBiu Server所提供的API,腳本檢測創建XXXXAppModule(空組件),獲取某個應用的某個版本的組件配置表安裝組件,完成打包工作。

統一配置

組件化帶來的一個問題,每個組件獨立存在,工程結構類似于這樣:

(點擊放大圖像)

Example for XXXX是當前組件在開發過程中所用來測試,驗證的demo代碼。

Pods為最終組件輸出實際工程區域。

編譯組合的組件,輸出最終的App,如果在某個版本需要更新icon或啟動圖怎么辦?總不可能讓每個組件在Example中修改這些資源或數據。

有沒有可以統一這些配置的辦法,而又沒必要讓開發者人為的替換管理?

答案是肯定的,我們在iBiu可視化工具中加入了應用版本的配置拉取腳本功能。簡單點講就是安裝組合時執行遠程腳本。在iBiu后臺針對應用版本配置相應的腳本,可以修改工程任意配置。

(點擊放大圖像)

這是一段檢測替換icon的腳本,通過安裝組件時可執行遠程腳本,能做的事情不僅限于替換個圖片這么簡單,由于可執行遠程腳本權限太大,該功能使用還是需要謹慎。

最后

該文章主要描述京東iOS組件管理的解決思路,由于內容過多,無法展開細講每個環節,讀者朋友如果對某些細節感興趣可以留言,我們有針對性的另開專題。

 

來自:http://www.infoq.com/cn/articles/jd-ios-component-management

 

 本文由用戶 AudreaWilhi 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!