組件化開(kāi)發(fā)

多人開(kāi)發(fā)创译,單一工程開(kāi)發(fā)模式顯露的問(wèn)題

  • 耦合比較嚴(yán)重(因?yàn)闆](méi)有明確的約束店溢,組件間引用的現(xiàn)象會(huì)比較多)
  • 容易出現(xiàn)沖突(尤其是使用Xib,還有就是Xcode Project矩桂,雖說(shuō)又腳本可以改善)
  • 業(yè)務(wù)方的開(kāi)發(fā)效率不夠高(只關(guān)心自己的組件沸移,卻要編譯整個(gè)項(xiàng)目,與其他不相干的代碼糅合在一起)

采取組件化策略帶來(lái)的好處

  • 加快編譯速度(不用編譯主客那一大坨代碼了)
  • 自由選擇開(kāi)發(fā)姿勢(shì)(MVC/MVVM/FRP)
  • 方便QA有針對(duì)性地測(cè)試
  • 提高業(yè)務(wù)開(kāi)發(fā)效率
    組件化大概的框架


    image.png

    組件化顧名思義就是把一個(gè)大的APP拆成一個(gè)個(gè)小的組件,相互之間不直接引用

實(shí)現(xiàn)方式

組件間的通信

以iOS為例雹锣,由于之前就是采用的URL跳轉(zhuǎn)模式网沾,理論上頁(yè)面之間的跳轉(zhuǎn)只需open一個(gè)URL即可。所以對(duì)于一個(gè)組件來(lái)說(shuō)蕊爵,只要定義支持哪些URL即可辉哥,比如詳情頁(yè),大概可以這么做

[MGJRouter registerURLPattern:@"mgj://detail?id=:id" toHandler:^(NSDictionary *routerParameters) {
    NSNumber *id = routerParameters[@"id"];
    // create view controller with id
    // push view controller
}]

在任何地方只需調(diào)用該方法在辆,傳入相應(yīng)的URL就可以打開(kāi)相應(yīng)的詳情頁(yè)证薇。
界面跳轉(zhuǎn)之間的URL可以在后臺(tái)進(jìn)行專(zhuān)門(mén)管理
然后可以把這些短鏈生成不同平臺(tái)所需的文件,iOS平臺(tái)生成.{h,m}文件匆篓,Android平臺(tái)生成.java文件浑度,并注入到項(xiàng)目中,這樣開(kāi)發(fā)人員只需在項(xiàng)目中打開(kāi)該文件就知道所有的可用URL了鸦概。

還有一種情況箩张,就是組件要調(diào)用組件B的某個(gè)方法,比如在商品詳情頁(yè)要展示購(gòu)買(mǎi)購(gòu)物車(chē)的商品數(shù)量窗市,就涉及到向購(gòu)物車(chē)組件拿數(shù)據(jù)先慷。
類(lèi)似這種同步調(diào)用,iOS之前采用的簡(jiǎn)單方案咨察,還是依托于MGJRouter论熙,不過(guò)添加了新的方法-(id)objectForURL:,注冊(cè)時(shí)也使用心得方法進(jìn)行注冊(cè)

[MGJRouter registerURLPattern:@"mgj://cart/ordercount" toObjectHandler:^id(NSDictionary *routerParamters){
    // do some calculation
    return @42;
}]

稍微復(fù)雜但更具通用性的方法是使用協(xié)議<->類(lèi)綁定的方式摄狱,還是以購(gòu)物車(chē)為例脓诡,購(gòu)物車(chē)組件可以提供這么個(gè)Protocol

@protocol MGJCart <NSObject>
+ (NSInteger)orderCount;
@end

可以看到通過(guò)協(xié)議可以直接指定返回的數(shù)據(jù)類(lèi)型。然后在購(gòu)物車(chē)組件內(nèi)再新建個(gè)類(lèi)實(shí)現(xiàn)這個(gè)協(xié)議媒役,假設(shè)這個(gè)類(lèi)名為MGJCarImpl祝谚,接著就可以把它與協(xié)議關(guān)聯(lián)起來(lái) [ModuleManager registerClass:MGJCartImpl forProtocol:@protocol(MGJCart)],對(duì)于使用方來(lái)說(shuō)酣衷,要拿到這個(gè) MGJCartImpl交惯,需要調(diào)用 [ModuleManager classForProtocol:@protocol(MGJCart)]。拿到之后再調(diào)用 + (NSInteger)orderCount 就可以了穿仪。
那么席爽,這個(gè)協(xié)議放在哪里比較合適呢?如果跟組件放在一起啊片,使用時(shí)還是要先引入組件拳昌,如果有多個(gè)這樣的組件就會(huì)比較麻煩了。所以我們把這些公共的協(xié)議統(tǒng)一放到了 PublicProtocolDomain.h 下钠龙,到時(shí)只依賴(lài)這一個(gè)文件就可以了。

組件的生命周期管理

理想中的組件可以很方便地集成到主客中,并且有跟AppDelegate一致的回調(diào)方法碴里。這也是moduleManager做的事情沈矿。
先來(lái)看看現(xiàn)在的入口方法

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [MGJApp startApp];

    [[ModuleManager sharedInstance] loadModuleFromPlist:[[NSBundle mainBundle] pathForResource:@"modules" ofType:@"plist"]];
    NSArray *modules = [[ModuleManager sharedInstance] allModules];
    for (id<ModuleProtocol> module in modules) {
        if ([module respondsToSelector:_cmd]) {
            [module application:application didFinishLaunchingWithOptions:launchOptions];
        }
    }
    
    [self trackLaunchTime];
    return YES;
}

其中 [MGJApp startApp] 主要負(fù)責(zé)一些 SDK 的初始化。[self trackLaunchTime] 是我們打的一個(gè)點(diǎn)咬腋,用來(lái)監(jiān)測(cè)從 main 方法開(kāi)始到入口方法調(diào)用結(jié)束花了多長(zhǎng)時(shí)間羹膳。其他的都由 ModuleManager 搞定,loadModuleFromPlist:pathForResource: 方法會(huì)讀取 bundle 里的一個(gè) plist 文件根竿,這個(gè)文件的內(nèi)容大概是這樣的


image.png

每個(gè)Module都實(shí)現(xiàn)了ModuleProtocol陵像,其中有一個(gè)- (BOOL)application: didFinishLaunchingWithOptions:方法,如果實(shí)現(xiàn)了的話寇壳,就會(huì)被調(diào)用醒颖。
還有一個(gè)問(wèn)題就是,系統(tǒng)的一些事件會(huì)有通知壳炎,比如 applicationDidBecomeActive 會(huì)有對(duì)應(yīng)的 UIApplicationDidBecomeActiveNotification泞歉,組件如果要做響應(yīng)的話,只需監(jiān)聽(tīng)這個(gè)系統(tǒng)通知即可匿辩。但也有一些事件是沒(méi)有通知的腰耙,比如 - application:didRegisterUserNotificationSettings:,這時(shí)組件如果也要做點(diǎn)事情铲球,怎么辦挺庞?

一個(gè)簡(jiǎn)單的解決方法是在 AppDelegate 的各個(gè)方法里,手動(dòng)調(diào)一遍組件的對(duì)應(yīng)的方法稼病,如果有就執(zhí)行选侨。

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
    NSArray *modules = [[ModuleManager sharedInstance] allModules];
    for (id<ModuleProtocol> module in modules) {
        if ([module respondsToSelector:_cmd]) {
            [module application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
        }
    }
}

殼工程

殼工程
既然已經(jīng)拆出去了,那拆出去的組件總得有個(gè)載體溯饵,這個(gè)載體就是殼工程侵俗,殼工程主要包含一些基礎(chǔ)組件和業(yè)務(wù)SDK,這也是主工程包含的一些內(nèi)容丰刊,所以如果在殼工程可以正常運(yùn)行的話隘谣,到了主工程也沒(méi)什么問(wèn)題。不過(guò)這里存在版本同步問(wèn)題啄巧,之后會(huì)說(shuō)到

組件拆分

由于之前的代碼都是在一個(gè)工程下的寻歧,所以要單獨(dú)拿出來(lái)作為一個(gè)組件就會(huì)遇到不少問(wèn)題。首先是組件的劃分秩仆,當(dāng)時(shí)在定義組件粒度時(shí)也花了些時(shí)間討論码泛,究竟是粒度粗點(diǎn)好,還是細(xì)點(diǎn)好澄耍。粗點(diǎn)的話比較有利于拆分噪珊,細(xì)點(diǎn)的話靈活度比較高晌缘。最終還是選擇粗一點(diǎn)的粒度,先拆出來(lái)再說(shuō)痢站。

假如要把詳情頁(yè)遷出來(lái)磷箕,就會(huì)發(fā)現(xiàn)它依賴(lài)了一些其他部分的代碼,那最快的方式就是直接把代碼拷過(guò)來(lái)阵难,改個(gè)名使用岳枷。比較簡(jiǎn)單暴力。說(shuō)起來(lái)比較簡(jiǎn)單呜叫,做的時(shí)候也是挺有挑戰(zhàn)的空繁,因?yàn)檎5臉I(yè)務(wù)并不會(huì)因?yàn)椤附M件化」而停止,所以開(kāi)發(fā)同學(xué)們需要同時(shí)兼顧正常的業(yè)務(wù)和組件的拆分

版本管理

我們的組件包括第三方庫(kù)都是通過(guò)Cocoapods來(lái)管理的朱庆,其中組件使用了私有庫(kù)抑诸。之所以選擇Cocoapods畦木,一個(gè)是因?yàn)樗容^方便仰挣,還有就是用戶基數(shù)比較大翘簇,且社區(qū)也比較活躍,當(dāng)然也有其他的管理方式维蒙,比如 submodule / subtree掰吕,在開(kāi)發(fā)人員比較多的情況下,方便颅痊、靈活的方案容易占上風(fēng)殖熟,雖然它也有自己的問(wèn)題。主要有版本同步和更新/編譯慢的問(wèn)題斑响。

假如基礎(chǔ)組件做了個(gè) API 接口升級(jí)菱属,這個(gè)升級(jí)會(huì)對(duì)原有的接口做改動(dòng),自然就會(huì)升一個(gè)中位的版本號(hào)舰罚,比如原先是 1.6.19纽门,那么現(xiàn)在就變成 1.7.0 了。而我們?cè)?Podfile 里都是用 ~ 指定的营罢,這樣就會(huì)出現(xiàn)主工程的 pod 版本升上去了赏陵,但是殼工程沒(méi)有同步到,然后群里就會(huì)各種反饋編譯不過(guò)饲漾,而且這個(gè)編譯不過(guò)的長(zhǎng)尾有時(shí)能拖上兩三天蝙搔。

然后我們就想了個(gè)辦法,如果不在殼工程里指定基礎(chǔ)庫(kù)的版本考传,只在主工程里指定呢吃型,理論上應(yīng)該可行,只要不出現(xiàn)某個(gè)基礎(chǔ)庫(kù)要同時(shí)維護(hù)多個(gè)版本的情況僚楞。但實(shí)踐中發(fā)現(xiàn)勤晚,殼工程有時(shí)會(huì)莫名其妙地升不上去枉层,在 podfile 里指定最新的版本又可以升上去,所以此路不通赐写。

還有一個(gè)問(wèn)題是 pod update 時(shí)間過(guò)長(zhǎng)返干,經(jīng)常會(huì)在 Analyzing Dependency 上卡 10 多分鐘,非常影響效率血淌。后來(lái)排查下來(lái)是跟組件的 Podspec 有關(guān),配置了 subspec财剖,且依賴(lài)比較多悠夯。

然后就是 pod update 之后的編譯,由于是源碼編譯躺坟,所以這塊的時(shí)間花費(fèi)也不少沦补,接下去會(huì)考慮 framework 的方式。

持續(xù)集成

在剛開(kāi)始咪橙,持續(xù)集成還不是很完善夕膀,業(yè)務(wù)方升級(jí)組件,直接把 podspec 扔到 private repo 里就完事了美侦。這樣最簡(jiǎn)單产舞,但也經(jīng)常會(huì)帶來(lái)編譯通不過(guò)的問(wèn)題。而且這種隨意的版本升級(jí)也不太能保證質(zhì)量菠剩。于是我們就搭建了一套持續(xù)集成系統(tǒng)易猫,大概如此


image.png

每個(gè)組件升級(jí)之前都需要先通過(guò)編譯,然后再?zèng)Q定是否升級(jí)具壮。這套體系看起來(lái)不復(fù)雜准颓,但在實(shí)施過(guò)程中經(jīng)常會(huì)遇到后端的并發(fā)問(wèn)題,導(dǎo)致業(yè)務(wù)方要么集成失敗棺妓,要么要等不少時(shí)間攘已。而且也沒(méi)有一個(gè)地方可以呈現(xiàn)當(dāng)前版本的組件版本信息。還有就是業(yè)務(wù)方對(duì)于這種命令行的升級(jí)方式接受度也不是很高怜跑。
[圖片上傳失敗...(image-5b946d-1560579679555)]
基于此样勃,在經(jīng)過(guò)了幾輪討論之后,有了新版的持續(xù)集成平臺(tái)妆艘,升級(jí)操作通過(guò)網(wǎng)頁(yè)端來(lái)完成彤灶。

大致思路是,業(yè)務(wù)方如果要升級(jí)組件批旺,假設(shè)現(xiàn)在的版本是 0.1.7幌陕,添加了一些 feature 之后,殼工程測(cè)試通過(guò)汽煮,想集成到主工程里看看效果搏熄,或者其他組件也想引用這個(gè)最新的棚唆,就可以在后臺(tái)手動(dòng)把版本升到 0.1.8-rc.1,這樣的話心例,原先依賴(lài) ~> 0.1.7 的組件宵凌,不會(huì)升到 0.1.8,同時(shí)想要測(cè)試這個(gè)組件的話止后,只要手動(dòng)把版本調(diào)到 0.1.8-rc.1 就可以了瞎惫。這個(gè)過(guò)程不會(huì)觸發(fā) CI 的編譯檢查。

當(dāng)測(cè)試通過(guò)后译株,就可以把尾部的 -rc.n 去掉瓜喇,然后點(diǎn)擊「集成」,就會(huì)走 CI 編譯檢查歉糜,通過(guò)的話乘寒,會(huì)在主工程的 podfile 里寫(xiě)上固定的版本號(hào) 0.1.8。也就是說(shuō)匪补,podfile 里所有的組件版本號(hào)都是固定的伞辛。


image.png

周邊設(shè)施

基礎(chǔ)組件及組件的文檔/Demo/單元測(cè)試

無(wú)限基礎(chǔ)的智能是為集團(tuán)提供解決方案,知識(shí)在蘑菇街App里能work是遠(yuǎn)遠(yuǎn)不夠的夯缺,所以就需要提供入口蚤氏,知道有哪些可用組件,并且如何使用喳逛,就像這樣(目前還未實(shí)現(xiàn))


image.png

這就要求組件的負(fù)責(zé)人需要及時(shí)地更新 README / CHANGELOG / API瞧捌,并且當(dāng)發(fā)生 API 變更時(shí),能夠快速通知到使用方润文。

公共 UI 組件

組件化之后還有一個(gè)問(wèn)題就是資源的重復(fù)性姐呐,以前在一個(gè)工程里的時(shí)候,資源都可以很方便地拿到典蝌,現(xiàn)在獨(dú)立出去了曙砂,也不知道哪些是公用的,哪些是獨(dú)有的骏掀,索性都放到自己的組件里鸠澈,這樣就會(huì)導(dǎo)致包變大。還有一個(gè)問(wèn)題是每個(gè)組件可能是不同的產(chǎn)品經(jīng)理在跟截驮,而他們很可能只關(guān)注于自己關(guān)心的頁(yè)面長(zhǎng)什么樣笑陈,而忽略了整體的樣式。公共 UI 組件就是用來(lái)解決這些問(wèn)題的葵袭,這些組件甚至可以跨 App 使用涵妥。(目前還未實(shí)現(xiàn))
[文章轉(zhuǎn)載自](https://limboy.me/tech/2016/03/10/mgj-components.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市坡锡,隨后出現(xiàn)的幾起案子蓬网,更是在濱河造成了極大的恐慌窒所,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帆锋,死亡現(xiàn)場(chǎng)離奇詭異吵取,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)锯厢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)皮官,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人实辑,你說(shuō)我怎么就攤上這事臣疑。” “怎么了徙菠?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)郁岩。 經(jīng)常有香客問(wèn)我婿奔,道長(zhǎng),這世上最難降的妖魔是什么问慎? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任萍摊,我火速辦了婚禮,結(jié)果婚禮上如叼,老公的妹妹穿的比我還像新娘冰木。我一直安慰自己,他們只是感情好笼恰,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布踊沸。 她就那樣靜靜地躺著,像睡著了一般社证。 火紅的嫁衣襯著肌膚如雪逼龟。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天追葡,我揣著相機(jī)與錄音腺律,去河邊找鬼。 笑死宜肉,一個(gè)胖子當(dāng)著我的面吹牛匀钧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播谬返,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼之斯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了朱浴?” 一聲冷哼從身側(cè)響起吊圾,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤达椰,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后项乒,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體啰劲,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年檀何,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蝇裤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡频鉴,死狀恐怖栓辜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情垛孔,我是刑警寧澤藕甩,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站周荐,受9級(jí)特大地震影響狭莱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜概作,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一腋妙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧讯榕,春花似錦骤素、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至霎槐,卻和暖如春规辱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背栽燕。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工罕袋, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人碍岔。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓浴讯,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親蔼啦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子榆纽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 不怕跌倒,所以飛翔 組件化開(kāi)發(fā) 參考資源 Android組件化方案 為什么要組件化開(kāi)發(fā) 解決問(wèn)題 實(shí)際業(yè)務(wù)變化非常...
    筆墨Android閱讀 2,968評(píng)論 0 0
  • 集成電路FDN338P介紹: 工作溫度:-55°C~150°C(TJ)FET功能:-柵源電壓 Vgss:±8V包裝...
    鮮花插在牛糞上閱讀 168評(píng)論 0 0
  • 1、應(yīng)盡量避免在 where 子句中使用!=或<>操作符奈籽,否則將引擎放棄使用索引而進(jìn)行全表掃描饥侵。 2、對(duì)查詢(xún)進(jìn)行優(yōu)...
    不挖石油的小小閱讀 258評(píng)論 0 1
  • 葉落紛飛衣屏,人間多少悲躏升。與君期,恨離別狼忱。愿為廬州月膨疏。人離散,影相隨钻弄,千里心相系佃却。 花敗草枯,無(wú)語(yǔ)話凄涼窘俺。伊人淚饲帅,難拭...
    小愛(ài)纏綿大愛(ài)放手閱讀 118評(píng)論 0 0
  • 也許自己經(jīng)歷這么多后,已經(jīng)忘了什么是快樂(lè)瘤泪,忍洒闸?能給我們帶來(lái)什么?活出自己吧均芽,你要懂得世界不該聽(tīng)名,但要質(zhì)樸单鹿,所謂的...
    YZ魚(yú)夢(mèng)Q閱讀 73評(píng)論 0 1