一. 組件化的需求
在 iOS Native app 前期開(kāi)發(fā)的時(shí)候,如果參與的開(kāi)發(fā)人員也不多,那么代碼大多數(shù)都是寫(xiě)在一個(gè)工程里面的蚀苛,這個(gè)時(shí)候業(yè)務(wù)發(fā)展也不是太快在验,所以很多時(shí)候也能保證開(kāi)發(fā)效率。
但是一旦項(xiàng)目工程龐大以后枉阵,開(kāi)發(fā)人員也會(huì)逐漸多起來(lái)译红,業(yè)務(wù)發(fā)展突飛猛進(jìn),這個(gè)時(shí)候單一的工程開(kāi)發(fā)模式就會(huì)暴露出弊端了兴溜。
- 項(xiàng)目?jī)?nèi)代碼文件耦合比較嚴(yán)重
- 容易出現(xiàn)沖突侦厚,大公司同時(shí)開(kāi)發(fā)一個(gè)項(xiàng)目的人多,每次 pull 一下最新代碼就會(huì)有很多沖突拙徽,有時(shí)候合并代碼需要半個(gè)小時(shí)左右刨沦,這會(huì)耽誤開(kāi)發(fā)效率。
- 業(yè)務(wù)方的開(kāi)發(fā)效率不夠高膘怕,開(kāi)發(fā)人員一多想诅,每個(gè)人都只想關(guān)心自己的組件,但是卻要編譯整個(gè)項(xiàng)目岛心,與其他不相干的代碼糅合在一起来破。調(diào)試起來(lái)也不方便,即使開(kāi)發(fā)一個(gè)很小的功能忘古,都要去把整個(gè)項(xiàng)目都編譯一遍徘禁,調(diào)試效率低。
為了解決這些問(wèn)題髓堪,iOS 項(xiàng)目就出現(xiàn)了組件化的概念送朱。所以 iOS 的組件化是為了解決上述這些問(wèn)題的,這里與前端組件化解決的痛點(diǎn)不同干旁。
iOS 組件化以后能帶來(lái)如下的好處:
- 加快編譯速度(不用編譯主客那一大坨代碼了驶沼,各個(gè)組件都是靜態(tài)庫(kù))
- 自由選擇開(kāi)發(fā)姿勢(shì)(MVC / MVVM / FRP)
- 方便 QA 有針對(duì)性地測(cè)試
- 提高業(yè)務(wù)開(kāi)發(fā)效率
iOS 組件化的封裝性只是其中的一小部分,更加關(guān)心的是如何拆分組件争群,如何解除耦合回怜。前端的組件化可能會(huì)更加注重組件的封裝性,高可復(fù)用性换薄。
二. 如何封裝組件
iOS 的組件化手段非常單一玉雾,就是利用 Cocoapods 封裝成 pod 庫(kù),主工程分別引用這些 pod 即可专控。越來(lái)越多的第三方庫(kù)也都在 Cocoapods 上發(fā)布自己的最新版本抹凳,大公司也在公司內(nèi)部維護(hù)了公司私有的 Cocoapods 倉(cāng)庫(kù)遏餐。一個(gè)封裝完美的 Pod 組件伦腐,主工程使用起來(lái)非常方便。
具體如果用 Cocoapods 打包一個(gè)靜態(tài)庫(kù) .a 或者 framework 失都,網(wǎng)上教程很多柏蘑,這里給一個(gè)鏈接幸冻,詳細(xì)的操作方法就不再贅述了。
最終想要達(dá)到的理想目標(biāo)就是主工程就是一個(gè)殼工程咳焚,其他所有代碼都在組件 Pods 里面洽损,主工程的工作就是初始化,加載這些組件的革半,沒(méi)有其他任何代碼了碑定。
三. 如何劃分組件
iOS 劃分組件雖然沒(méi)有一個(gè)很明確的標(biāo)準(zhǔn),因?yàn)槊總€(gè)項(xiàng)目都不同又官,劃分組件的粗粒度也不同延刘,但是依舊有一個(gè)劃分的原則。
App之間可以重用的 Util六敬、Category碘赖、網(wǎng)絡(luò)層和本地存儲(chǔ) storage 等等這些東西抽成了 Pod 庫(kù)。還有些一些和業(yè)務(wù)相關(guān)的外构,也是在各個(gè)App之間重用的普泡。
原則就是:要在App之間共享的代碼就應(yīng)該抽成 Pod 庫(kù),把它們作為一個(gè)個(gè)組件审编。不在 App 間共享的業(yè)務(wù)線撼班,也應(yīng)該抽成 Pod,解除它與工程其他的文件耦合性割笙。
常見(jiàn)的劃分方法都是從底層開(kāi)始動(dòng)手权烧,網(wǎng)絡(luò)庫(kù),路由伤溉,MVVM框架般码,數(shù)據(jù)庫(kù)存儲(chǔ),加密解密乱顾,工具類板祝,地圖,基礎(chǔ)SDK走净,APM券时,風(fēng)控,埋點(diǎn)……從下往上伏伯,到了上層就是各個(gè)業(yè)務(wù)方的組件了橘洞,最常見(jiàn)的就類似于購(gòu)物車,我的錢(qián)包说搅,登錄炸枣,注冊(cè)等。
四. 組件化原理
iOS 的組件化原理是基于 Cocoapods 的。關(guān)于 Cocoapods 的具體工作原理适肠,可以看這篇文章《CocoaPods 都做了什么霍衫?》。
這里簡(jiǎn)單的分析一下 pod進(jìn)來(lái)的庫(kù)是什么加載到主工程的侯养。
pod 會(huì)依據(jù) Podfile 文件里面的依賴庫(kù)敦跌,把這些庫(kù)的源代碼下載下來(lái),并創(chuàng)建好 Pods workspace逛揩。當(dāng)程序編譯的時(shí)候柠傍,會(huì)預(yù)先執(zhí)行2個(gè) pod設(shè)置進(jìn)來(lái)的腳本。
在上面這個(gè)腳本中辩稽,會(huì)把 Pods 里面的打包好的靜態(tài)庫(kù)合并到 libPods-XXX.a 這個(gè)靜態(tài)庫(kù)里面携兵,這個(gè)庫(kù)是主工程依賴的庫(kù)。
<figure>上圖就是給主項(xiàng)目加載 Pods 庫(kù)的腳本搂誉。
Pods 另外一個(gè)腳本是加載資源的徐紧。見(jiàn)下圖。
這里加載的資源是 Pods 庫(kù)里面的一些圖片資源炭懊,或者是 Boudle 里面的 xib 并级,storyboard,音樂(lè)資源等等侮腹。這些資源也會(huì)一起打到 libPods-XXX.a 這個(gè)靜態(tài)庫(kù)里面嘲碧。
上圖就是加載資源的腳本。
五. 組件分類
iOS 的組件主要分為2種形式:
- 靜態(tài)庫(kù)
- 動(dòng)態(tài)庫(kù)
靜態(tài)庫(kù)一般是以 .a 和 .framework 結(jié)尾的文件父阻,動(dòng)態(tài)庫(kù)一般是以 .dylib 和 .framework 結(jié)尾的文件愈涩。
這里可以看到,一個(gè) .framework 結(jié)尾的文件僅僅通過(guò)文件類型是無(wú)法判斷出它是一個(gè)靜態(tài)庫(kù)還是一個(gè)動(dòng)態(tài)庫(kù)加矛。
靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)的區(qū)別在于:
.a文件肯定是靜態(tài)庫(kù)履婉,.dylib肯定是動(dòng)態(tài)庫(kù),.framework可能是靜態(tài)庫(kù)也可能是動(dòng)態(tài)庫(kù)斟览;
靜態(tài)庫(kù)在鏈接其他庫(kù)的情況時(shí)毁腿,它會(huì)被完整的復(fù)制到可執(zhí)行文件中,如果多個(gè)App都使用了同一個(gè)靜態(tài)庫(kù)苛茂,那么每個(gè)App都會(huì)拷貝一份已烤,缺點(diǎn)是浪費(fèi)內(nèi)存。類似于定義一個(gè)基本變量妓羊,使用該基本變量是是新復(fù)制了一份數(shù)據(jù)胯究,而不是原來(lái)定義的;靜態(tài)庫(kù)的好處很明顯躁绸,編譯完成之后裕循,庫(kù)文件實(shí)際上就沒(méi)有作用了丙猬。目標(biāo)程序沒(méi)有外部依賴,直接就可以運(yùn)行费韭。當(dāng)然其缺點(diǎn)也很明顯,就是會(huì)使用目標(biāo)程序的體積增大庭瑰。
動(dòng)態(tài)庫(kù)不會(huì)被復(fù)制星持,只有一份,程序運(yùn)行時(shí)動(dòng)態(tài)加載到內(nèi)存中弹灭,系統(tǒng)只會(huì)加載一次督暂,多個(gè)程序共用一份,節(jié)約了內(nèi)存穷吮。而且使用動(dòng)態(tài)庫(kù)逻翁,可以不重新編譯連接可執(zhí)行程序的前提下,更新動(dòng)態(tài)庫(kù)文件達(dá)到更新應(yīng)用程序的目的捡鱼。
六. 組件間的消息傳遞和狀態(tài)管理
之前我們討論過(guò)了八回,iOS 組件化十分關(guān)注解耦性,這算是組件化的一個(gè)重要目的驾诈。iOS 各個(gè)組件之間消息傳遞是用路由來(lái)實(shí)現(xiàn)的缠诅。關(guān)于路由,筆者曾經(jīng)寫(xiě)過(guò)一篇比較詳細(xì)的文章乍迄,感興趣的可以來(lái)看這篇文章《iOS 組件化 —— 路由設(shè)計(jì)思路分析》管引。
七. 組件注冊(cè)方式
iOS 組件注冊(cè)的方式主要有3種:
- load方法注冊(cè)
- 讀取 plist 文件注冊(cè)
- Annotation注解方式注冊(cè)
前兩種方式都比較簡(jiǎn)單,容易理解闯两。
第一種方式在 load 方法里面利用 Runtime 把組件名和組件實(shí)例的映射關(guān)系保存到一個(gè)全局的字典里褥伴,方便程序啟動(dòng)以后可以隨時(shí)調(diào)用。
第二種方式是把組件名和組件實(shí)例的映射關(guān)系預(yù)先寫(xiě)在 plist 文件中漾狼。程序需要的時(shí)候直接去讀取這個(gè) plist 文件重慢。plist 文件可以從服務(wù)器讀取過(guò)來(lái),這樣 App 還能有一定的動(dòng)態(tài)性逊躁。
第三種方式比較黑科技伤锚。利用的是 Mach-o 的數(shù)據(jù)結(jié)構(gòu),在程序編程鏈接成可執(zhí)行文件的時(shí)候志衣,就把相關(guān)注冊(cè)信息直接寫(xiě)入到最終的可執(zhí)行文件的 Data 數(shù)據(jù)段內(nèi)屯援。程序執(zhí)行以后,直接去那個(gè)段內(nèi)去讀取想要的數(shù)據(jù)即可念脯。
關(guān)于這三種做法的詳細(xì)實(shí)現(xiàn)狞洋,可以看筆者之前的一篇文章《BeeHive —— 一個(gè)優(yōu)雅但還在完善中的解耦框架》,在這篇文章里面詳細(xì)的分析了上述3種注冊(cè)過(guò)程的具體實(shí)現(xiàn)绿店。
文中鏈接都已失效,想看的話留言發(fā)給你新的地址
作者:一縷殤流化隱半邊冰霜