蘑菇街 App 的組件化之路

原文地址:蘑菇街 App 的組件化之路

在組件化之前蕾久,蘑菇街 App 的代碼都是在一個(gè)工程里開發(fā)的舍杜,在人比較少,業(yè)務(wù)發(fā)展不是很快的時(shí)候桅滋,這樣是比較合適的慧耍,能一定程度地保證開發(fā)效率。

慢慢地代碼量多了起來,開發(fā)人員也多了起來蜂绎,業(yè)務(wù)發(fā)展也快了起來栅表,這時(shí)單一工程開發(fā)模式就會(huì)顯露出一些弊端

1.耦合比較嚴(yán)重(因?yàn)闆]有明確的約束笋鄙,「組件」間引用的現(xiàn)象會(huì)比較多)

2.容易出現(xiàn)沖突(尤其是使用 Xib师枣,還有就是 Xcode Project,雖說有腳本可以改善)

3.業(yè)務(wù)方的開發(fā)效率不夠高(只關(guān)心自己的組件萧落,卻要編譯整個(gè)項(xiàng)目践美,與其他不相干的代碼糅合在一起)

為了解決這些問題,就采取了「組件化」策略找岖。它能帶來這些好處

1.加快編譯速度(不用編譯主客那一大坨代碼了)

2.自由選擇開發(fā)姿勢(shì)(MVC / MVVM / FRP)

3.方便 QA 有針對(duì)性地測(cè)試

4.提高業(yè)務(wù)開發(fā)效率

先來看下陨倡,組件化之后的一個(gè)大概架構(gòu)

「組件化」顧名思義就是把一個(gè)大的 App 拆成一個(gè)個(gè)小的組件,相互之間不直接引用许布。那如何做呢兴革?

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

組件間通信

以 iOS 為例,由于之前就是采用的 URL 跳轉(zhuǎn)模式蜜唾,理論上頁(yè)面之間的跳轉(zhuǎn)只需 open 一個(gè) URL 即可杂曲。所以對(duì)于一個(gè)組件來說,只要定義「支持哪些 URL」即可袁余,比如詳情頁(yè)擎勘,大概可以這么做的

首頁(yè)只需調(diào)用?[MGJRouter openURL:@"mgj://detail?id=404"]?就可以打開相應(yīng)的詳情頁(yè)。

那問題又來了颖榜,我怎么知道有哪些可用的 URL棚饵?為此,我們做了一個(gè)后臺(tái)專門來管理掩完。

然后可以把這些短鏈生成不同平臺(tái)所需的文件噪漾,iOS 平臺(tái)生成 .{h,m} 文件,Android 平臺(tái)生成 .java 文件且蓬,并注入到項(xiàng)目中欣硼。這樣開發(fā)人員只需在項(xiàng)目中打開該文件就知道所有的可用 URL 了。

目前還有一塊沒有做缅疟,就是參數(shù)這塊分别,雖然描述了短鏈,但真想要生成完整的 URL存淫,還需要知道如何傳參數(shù)耘斩,這個(gè)正在開發(fā)中。

還有一種情況會(huì)稍微麻煩點(diǎn)桅咆,就是「組件A」要調(diào)用「組件B」的某個(gè)方法括授,比如在商品詳情頁(yè)要展示購(gòu)物車的商品數(shù)量,就涉及到向購(gòu)物車組件拿數(shù)據(jù)。

類似這種同步調(diào)用荚虚,iOS 之前采用了比較簡(jiǎn)單的方案薛夜,還是依托于?MGJRouter,不過添加了新的方法?- (id)objectForURL:版述,注冊(cè)時(shí)也使用新的方法進(jìn)行注冊(cè)

使用時(shí)?NSNumber *orderCount = [MGJRouter objectForURL:@"mgj://cart/ordercount"]?這樣就拿到了購(gòu)物車?yán)锏纳唐窋?shù)梯澜。

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

可以看到通過協(xié)議可以直接指定返回的數(shù)據(jù)類型晚伙。然后在購(gòu)物車組件內(nèi)再新建個(gè)類實(shí)現(xiàn)這個(gè)協(xié)議,假設(shè)這個(gè)類名為MGJCartImpl俭茧,接著就可以把它與協(xié)議關(guān)聯(lián)起來?[ModuleManagerregisterClass:MGJCartImpl?forProtocol:@protocol(MGJCart)]咆疗,對(duì)于使用方來說,要拿到這個(gè)?MGJCartImpl母债,需要調(diào)用?[ModuleManagerclassForProtocol:@protocol(MGJCart)]午磁。拿到之后再調(diào)用?+ (NSInteger)orderCount就可以了。

那么毡们,這個(gè)協(xié)議放在哪里比較合適呢迅皇?如果跟組件放在一起,使用時(shí)還是要先引入組件漏隐,如果有多個(gè)這樣的組件就會(huì)比較麻煩了喧半。所以我們把這些公共的協(xié)議統(tǒng)一放到了?PublicProtocolDomain.h?下,到時(shí)只依賴這一個(gè)文件就可以了青责。

Android 也是采用類似的方式挺据。

組件生命周期管理

理想中的組件可以很方便地集成到主客中,并且有跟?AppDelegate?一致的回調(diào)方法脖隶。這也是?ModuleManager?做的事情扁耐。

先來看看現(xiàn)在的入口方法

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

每個(gè)?Module?都實(shí)現(xiàn)了?ModuleProtocol王暗,其中有一個(gè)?- (BOOL)applicaiton:didFinishLaunchingWithOptions:?方法,如果實(shí)現(xiàn)了的話庄敛,就會(huì)被調(diào)用俗壹。

還有一個(gè)問題就是,系統(tǒng)的一些事件會(huì)有通知藻烤,比如?applicationDidBecomeActive?會(huì)有對(duì)應(yīng)的?UIApplicationDidBecomeActiveNotification绷雏,組件如果要做響應(yīng)的話头滔,只需監(jiān)聽這個(gè)系統(tǒng)通知即可。但也有一些事件是沒有通知的涎显,比如?- application:didRegisterUserNotificationSettings:坤检,這時(shí)組件如果也要做點(diǎn)事情,怎么辦期吓?

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

殼工程


既然已經(jīng)拆出去了缺前,那拆出去的組件總得有個(gè)載體蛀醉,這個(gè)載體就是殼工程悬襟,殼工程主要包含一些基礎(chǔ)組件和業(yè)務(wù)SDK,這也是主工程包含的一些內(nèi)容拯刁,所以如果在殼工程可以正常運(yùn)行的話脊岳,到了主工程也沒什么問題。不過這里存在版本同步問題垛玻,之后會(huì)說到割捅。

遇到的問題


組件拆分


由于之前的代碼都是在一個(gè)工程下的,所以要單獨(dú)拿出來作為一個(gè)組件就會(huì)遇到不少問題帚桩。首先是組件的劃分亿驾,當(dāng)時(shí)在定義組件粒度時(shí)也花了些時(shí)間討論,究竟是粒度粗點(diǎn)好账嚎,還是細(xì)點(diǎn)好莫瞬。粗點(diǎn)的話比較有利于拆分,細(xì)點(diǎn)的話靈活度比較高郭蕉。最終還是選擇粗一點(diǎn)的粒度疼邀,先拆出來再說。

假如要把詳情頁(yè)遷出來召锈,就會(huì)發(fā)現(xiàn)它依賴了一些其他部分的代碼旁振,那最快的方式就是直接把代碼拷過來,改個(gè)名使用涨岁。比較簡(jiǎn)單暴力拐袜。說起來比較簡(jiǎn)單,做的時(shí)候也是挺有挑戰(zhàn)的梢薪,因?yàn)檎5臉I(yè)務(wù)并不會(huì)因?yàn)椤附M件化」而停止蹬铺,所以開發(fā)同學(xué)們需要同時(shí)兼顧正常的業(yè)務(wù)和組件的拆分。

版本管理


我們的組件包括第三方庫(kù)都是通過 Cocoapods 來管理的沮尿,其中組件使用了私有庫(kù)丛塌。之所以選擇 Cocoapods较解,一個(gè)是因?yàn)樗容^方便,還有就是用戶基數(shù)比較大赴邻,且社區(qū)也比較活躍(活躍到了會(huì)時(shí)不時(shí)地觸發(fā) Github 的 rate limit印衔,導(dǎo)致長(zhǎng)時(shí)間 clone 不下來···?見此),當(dāng)然也有其他的管理方式姥敛,比如 submodule / subtree奸焙,在開發(fā)人員比較多的情況下,方便彤敛、靈活的方案容易占上風(fēng)与帆,雖然它也有自己的問題。主要有版本同步和更新/編譯慢的問題墨榄。

假如基礎(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 版本升上去了郭卫,但是殼工程沒有同步到,然后群里就會(huì)各種反饋編譯不過背稼,而且這個(gè)編譯不過的長(zhǎng)尾有時(shí)能拖上兩三天贰军。

然后我們就想了個(gè)辦法,如果不在殼工程里指定基礎(chǔ)庫(kù)的版本蟹肘,只在主工程里指定呢词疼,理論上應(yīng)該可行,只要不出現(xiàn)某個(gè)基礎(chǔ)庫(kù)要同時(shí)維護(hù)多個(gè)版本的情況疆前。但實(shí)踐中發(fā)現(xiàn)寒跳,殼工程有時(shí)會(huì)莫名其妙地升不上去,在 podfile 里指定最新的版本又可以升上去竹椒,所以此路不通童太。

還有一個(gè)問題是?pod update?時(shí)間過長(zhǎng),經(jīng)常會(huì)在?Analyzing Dependency?上卡 10 多分鐘胸完,非常影響效率书释。后來排查下來是跟組件的 Podspec 有關(guān),配置了 subspec赊窥,且依賴比較多爆惧。

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

持續(xù)集成

在剛開始,持續(xù)集成還不是很完善熄阻,業(yè)務(wù)方升級(jí)組件斋竞,直接把 podspec 扔到 private repo 里就完事了。這樣最簡(jiǎn)單秃殉,但也經(jīng)常會(huì)帶來編譯通不過的問題坝初。而且這種隨意的版本升級(jí)也不太能保證質(zhì)量。于是我們就搭建了一套持續(xù)集成系統(tǒng)钾军,大概如此

每個(gè)組件升級(jí)之前都需要先通過編譯鳄袍,然后再?zèng)Q定是否升級(jí)。這套體系看起來不復(fù)雜吏恭,但在實(shí)施過程中經(jīng)常會(huì)遇到后端的并發(fā)問題拗小,導(dǎo)致業(yè)務(wù)方要么集成失敗,要么要等不少時(shí)間砸泛。而且也沒有一個(gè)地方可以呈現(xiàn)當(dāng)前版本的組件版本信息十籍。還有就是業(yè)務(wù)方對(duì)于這種命令行的升級(jí)方式接受度也不是很高。

基于此唇礁,在經(jīng)過了幾輪討論之后,有了新版的持續(xù)集成平臺(tái)惨篱,升級(jí)操作通過網(wǎng)頁(yè)端來完成盏筐。

大致思路是,業(yè)務(wù)方如果要升級(jí)組件砸讳,假設(shè)現(xiàn)在的版本是 0.1.7琢融,添加了一些 feature 之后,殼工程測(cè)試通過簿寂,想集成到主工程里看看效果漾抬,或者其他組件也想引用這個(gè)最新的,就可以在后臺(tái)手動(dòng)把版本升到 0.1.8-rc.1常遂,這樣的話纳令,原先依賴?~> 0.1.7?的組件,不會(huì)升到 0.1.8克胳,同時(shí)想要測(cè)試這個(gè)組件的話平绩,只要手動(dòng)把版本調(diào)到 0.1.8-rc.1 就可以了。這個(gè)過程不會(huì)觸發(fā) CI 的編譯檢查漠另。

當(dāng)測(cè)試通過后捏雌,就可以把尾部的?-rc.n?去掉,然后點(diǎn)擊「集成」笆搓,就會(huì)走 CI 編譯檢查性湿,通過的話纬傲,會(huì)在主工程的 podfile 里寫上固定的版本號(hào) 0.1.8。也就是說肤频,podfile 里所有的組件版本號(hào)都是固定的嘹锁。

周邊設(shè)施

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


無線基礎(chǔ)的職能是為集團(tuán)提供解決方案,只是在蘑菇街 App 里能 work 是遠(yuǎn)遠(yuǎn)不夠的着裹,所以就需要提供入口领猾,知道有哪些可用組件,并且如何使用骇扇,就像這樣(目前還未實(shí)現(xiàn))

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

公共 UI 組件


組件化之后還有一個(gè)問題就是資源的重復(fù)性继低,以前在一個(gè)工程里的時(shí)候,資源都可以很方便地拿到稍走,現(xiàn)在獨(dú)立出去了袁翁,也不知道哪些是公用的,哪些是獨(dú)有的婿脸,索性都放到自己的組件里粱胜,這樣就會(huì)導(dǎo)致包變大。還有一個(gè)問題是每個(gè)組件可能是不同的產(chǎn)品經(jīng)理在跟狐树,而他們很可能只關(guān)注于自己關(guān)心的頁(yè)面長(zhǎng)什么樣焙压,而忽略了整體的樣式。公共 UI 組件就是用來解決這些問題的抑钟,這些組件甚至可以跨 App 使用涯曲。(目前還未實(shí)現(xiàn))

小結(jié)

「組件化」是 App 膨脹到一定體積后的解決方案,能一定程度上解決問題在塔,在提高開發(fā)效率的過程中幻件,采坑是難免的,希望這篇文章能夠帶來些幫助蛔溃。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末绰沥,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子城榛,更是在濱河造成了極大的恐慌揪利,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件狠持,死亡現(xiàn)場(chǎng)離奇詭異疟位,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)喘垂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門甜刻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绍撞,“玉大人,你說我怎么就攤上這事得院∩迪常” “怎么了?”我有些...
    開封第一講書人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵祥绞,是天一觀的道長(zhǎng)非洲。 經(jīng)常有香客問我,道長(zhǎng)蜕径,這世上最難降的妖魔是什么两踏? 我笑而不...
    開封第一講書人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮兜喻,結(jié)果婚禮上梦染,老公的妹妹穿的比我還像新娘。我一直安慰自己朴皆,他們只是感情好帕识,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著遂铡,像睡著了一般肮疗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上忧便,一...
    開封第一講書人閱讀 52,246評(píng)論 1 308
  • 那天族吻,我揣著相機(jī)與錄音,去河邊找鬼珠增。 笑死,一個(gè)胖子當(dāng)著我的面吹牛砍艾,可吹牛的內(nèi)容都是我干的蒂教。 我是一名探鬼主播,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼脆荷,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼凝垛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蜓谋,我...
    開封第一講書人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤梦皮,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后桃焕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體剑肯,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年观堂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了让网。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呀忧。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖溃睹,靈堂內(nèi)的尸體忽然破棺而出而账,到底是詐尸還是另有隱情,我是刑警寧澤因篇,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布泞辐,位于F島的核電站,受9級(jí)特大地震影響竞滓,放射性物質(zhì)發(fā)生泄漏咐吼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一虽界、第九天 我趴在偏房一處隱蔽的房頂上張望汽烦。 院中可真熱鬧,春花似錦莉御、人聲如沸撇吞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)牍颈。三九已至,卻和暖如春琅关,著一層夾襖步出監(jiān)牢的瞬間煮岁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工涣易, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留画机,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓新症,卻偏偏與公主長(zhǎng)得像步氏,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子徒爹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359

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

  • 作者:limboy文章源自:http://limboy.me/ios/2016/03/10/mgj-compone...
    IT程序獅閱讀 12,897評(píng)論 5 39
  • 在組件化之前荚醒,蘑菇街 App 的代碼都是在一個(gè)工程里開發(fā)的,在人比較少隆嗅,業(yè)務(wù)發(fā)展不是很快的時(shí)候界阁,這樣是比較合適的,...
    yuditxj閱讀 547評(píng)論 0 1
  • Android組件化項(xiàng)目地址:Android組件化項(xiàng)目AndroidModulePattern Android組件...
    半灬邊灬天閱讀 2,924評(píng)論 4 37
  • 不怕跌倒胖喳,所以飛翔 組件化開發(fā) 參考資源 Android組件化方案 為什么要組件化開發(fā) 解決問題 實(shí)際業(yè)務(wù)變化非常...
    筆墨Android閱讀 2,989評(píng)論 0 0
  • 人生泡躯,生來孤獨(dú)。一直往前走吧,別害怕精续,盡管你孤身一人坝锰。
    金桔檸檬水果茶閱讀 90評(píng)論 0 0