理論篇
什么是組件化
組件化開(kāi)發(fā)就是將一個(gè)臃腫的褪储、單一的項(xiàng)目滓走,根據(jù)功能/業(yè)務(wù)/技術(shù)等等進(jìn)行拆分权薯,形成一個(gè)個(gè)獨(dú)立的功能組件讼呢,然后借助 CocoaPods 管理工具將其任意組合撩鹿,集成一個(gè)完整的項(xiàng)目。
你可以將 AFNetworking悦屏、SDWebImage 等等三方庫(kù)理解為自己項(xiàng)目的一部分节沦,屬于基礎(chǔ)組件部分,而我們要做的就是將項(xiàng)目劃分成多個(gè)獨(dú)立功能模塊窜管,再集成為一個(gè)完整的項(xiàng)目散劫。這一過(guò)程看似多此一舉稚机,但是帶來(lái)的優(yōu)勢(shì)卻是非常大幕帆。
為什么需要組件化
項(xiàng)目初期,功能相對(duì)簡(jiǎn)單赖条,普通的MVC+模塊文件分割就可以滿(mǎn)足絕大部分的需求失乾。但是隨著功能需求越來(lái)越多,業(yè)務(wù)越來(lái)越復(fù)雜多樣纬乍,現(xiàn)有的架構(gòu)已經(jīng)不太適用了碱茁,即使使用了 Git 分支管理,依然經(jīng)常發(fā)生合并沖突等等問(wèn)題仿贬,另外后期的維護(hù)成本也大大增加纽竣,業(yè)務(wù)邏輯變得復(fù)雜、模塊之間耦合度很大、查找問(wèn)題效率變低蜓氨、項(xiàng)目編譯過(guò)程過(guò)慢…… 而且伴隨著開(kāi)發(fā)人員的增多(多個(gè)小組之間協(xié)作開(kāi)發(fā))聋袋,這些問(wèn)題尤為突出,優(yōu)化開(kāi)發(fā)結(jié)構(gòu)變得非常重要穴吹。
中間層
針對(duì)上面的問(wèn)題幽勒,第一個(gè)想到的優(yōu)化就是新增一個(gè)中間層來(lái)協(xié)調(diào)各個(gè)模塊之間的調(diào)用,所有的模塊都通過(guò)這個(gè)中間層去實(shí)現(xiàn)調(diào)用和交互港令,但是這樣雖然一定程度上降低了模塊與模塊的之間的耦合度啥容,但是耦合都轉(zhuǎn)嫁到了中間層上了,并且中間層的改動(dòng)只能由一個(gè)人操作顷霹,否則非常容易發(fā)生沖突咪惠,本質(zhì)上并沒(méi)有發(fā)生多少變化。另外一點(diǎn)淋淀,查找問(wèn)題的效率低下硝逢、編譯過(guò)慢等問(wèn)題依舊沒(méi)有得到有效的解決。
這是傳統(tǒng)的中介者模式绅喉,這個(gè)中間層會(huì)依賴(lài)其他組件渠鸽,其他組件也會(huì)依賴(lài)中間層完成服務(wù)。
組件化
組件化能夠幫助我們將過(guò)大的項(xiàng)目拆解成數(shù)個(gè)小組件柴罐,開(kāi)發(fā)者只需要關(guān)注于組件所依賴(lài)的其他組件徽缚,而無(wú)需關(guān)心完整項(xiàng)目的其他部分,每個(gè)組件可以自己采取所習(xí)慣的架構(gòu)模式:MVC革屠、MVVM凿试、MVCS等等,就像開(kāi)發(fā)一款個(gè)人獨(dú)立的app那樣自由似芝。
項(xiàng)目組件化之后所帶來(lái)的好處是非常多的那婉,我們先總結(jié)一下非組件化所造成的問(wèn)題:
非組件化:
- 代碼高耦合度、高依賴(lài)
- 項(xiàng)目復(fù)雜党瓮、臃腫详炬、編譯過(guò)長(zhǎng)(影響調(diào)試)
- 難以融合/集成其他產(chǎn)品
- 需要統(tǒng)一架構(gòu)
……
組件化:
- 代碼復(fù)用性提高,可方便的集成到其他項(xiàng)目
- 項(xiàng)目可配置寞奸,方便集成和功能回退(指定版本)
- 化整為零呛谜,將項(xiàng)目細(xì)小化
- 方便組件的并行開(kāi)發(fā)
- 可方便做單元測(cè)試
- 組件自由度高,即插即用
……
當(dāng)然組件化也有著它的缺點(diǎn)枪萄,對(duì)已有的項(xiàng)目實(shí)施組件化架構(gòu)比較困難隐岛,耗費(fèi)時(shí)間長(zhǎng),項(xiàng)目組成員需要一定學(xué)習(xí)成本瓷翻;組件化并沒(méi)有相應(yīng)的標(biāo)準(zhǔn)聚凹,拆分的粒度要適中割坠,拆分粒度過(guò)高,則讓項(xiàng)目變得復(fù)雜妒牙,起到了反作用效果韭脊,反之,粒度過(guò)低单旁,體現(xiàn)不了組件化的優(yōu)勢(shì)沪羔,在項(xiàng)目業(yè)務(wù)不斷地添加的過(guò)程中,進(jìn)行不斷的嘗試調(diào)整象浑,找到適合自己項(xiàng)目的才是最好的蔫饰。
組件化的分層
項(xiàng)目組件化中,最難把握的就是粒度問(wèn)題愉豺,這需要開(kāi)發(fā)的自己的經(jīng)驗(yàn)去把控篓吁。這里只給出個(gè)人認(rèn)為的層次的劃分。
【基礎(chǔ)組件】:宏定義/常量/自定義工具類(lèi)蚪拦,如常用的自定義分類(lèi)
【功能組件】:項(xiàng)目中所用到的功能杖剪,如地圖定位/消息推送/分享等
【業(yè)務(wù)組件】:項(xiàng)目中的模塊/業(yè)務(wù),如聊天室/直播間/個(gè)人中心等
【中間組件】:負(fù)責(zé)項(xiàng)目中的路由/消息通知/傳參/回調(diào)等
【宿主工程】:項(xiàng)目容器驰贷,用來(lái)集成組件盛嘿,調(diào)整各個(gè)組件之間的消息傳遞的容器。
中間層的幾種方案
在組件化過(guò)程中括袒,中間層是各個(gè)組件的通信的橋梁次兆,中間層在組件化過(guò)程中扮演著非常重要的角色。目前關(guān)于中間層的設(shè)計(jì)筆者已知的有以下三種方式:基于URL Scheme
的路由锹锰、基于Runtime
的target-action
芥炭、面向接口。
- 路由
iOS 中支持的 URL Scheme
讓我們能夠在應(yīng)用之間恃慧、應(yīng)用內(nèi)部傳遞消息园蝠。日常開(kāi)發(fā)過(guò)程中經(jīng)常用到的就是調(diào)用系統(tǒng)服務(wù)、喚起三方app等等痢士,這些屬于應(yīng)用之間的消息傳遞彪薛,而我們這里借助 URL Scheme
完成應(yīng)用內(nèi)部的消息傳遞。這里的路由 URL 遵循網(wǎng)上通用的資源標(biāo)識(shí)符合 URI良瞧,如:appscheme://home/scan?param=value陪汽,我們通過(guò) URL
來(lái)傳遞信息,下層服務(wù)方通過(guò) URL 獲取參數(shù)提供服務(wù)褥蚯,上層消費(fèi)者通過(guò) URL 獲取到服務(wù),完成調(diào)用况增。
基于 URL Scheme
的三方庫(kù)
JLRoutes 是一種基于 URL Scheme 的路由框架赞庶,它全局會(huì)保存一個(gè)Map,key 是路由協(xié)議 url,value 則是對(duì) url 解析后 block 回調(diào)歧强,你可以在該回調(diào)中處理具體的業(yè)務(wù)澜薄。
實(shí)例:
例如我們的路由協(xié)議定義如下:
scheme://描述/打開(kāi)方式/保留字段/功能標(biāo)識(shí)?參數(shù)1=值1&參數(shù)2=值2
||
myroute://market/1/route/cjpm?stockcode=600212.ss
首先配置路由 url 和 對(duì)應(yīng)的回調(diào)處理:
/// 默認(rèn)下都會(huì)進(jìn)入這里摊册,這里填寫(xiě)路由匹配規(guī)則
[JLRoutes.globalRoutes addRoute:@"/market/:operate/route/:code" handler:^BOOL(NSDictionary<NSString *,id> * _Nonnull parameters) {
NSLog(@"%@", parameters);
// 接下來(lái)的業(yè)務(wù)邏輯
return YES; // 返回YES肤京,表示處理截止,后面的路由規(guī)則不再啟用
}];
然后在需要路由的地方傳入相應(yīng)的路由 url :
// 某地方獲取到的url
NSURL* url = [NSURL URLWithString:@"myroute://market/1/route/cjpm?stockcode=600212.ss"];
// 處理路由
[JLRoutes routeURL:url];
基于Runtime
的target-action
相比 url scheme
的提前注冊(cè)茅特、實(shí)現(xiàn)服務(wù)忘分,CTMediator
借助 OC 運(yùn)行時(shí)的特性,現(xiàn)實(shí)組件之間服務(wù)的自動(dòng)發(fā)現(xiàn)白修,無(wú)需提前注冊(cè)即可實(shí)現(xiàn)組件間的調(diào)用妒峦,因此,這種方案的可維護(hù)性兵睛、可讀性肯骇、擴(kuò)展性相對(duì)較高。
官方的 Demo 中祖很,結(jié)構(gòu)是這樣的:
CTMediator 的使用流程大體是這樣的:
底層組件
創(chuàng)建
Target_
開(kāi)頭的目標(biāo)類(lèi)笛丙,如Target_A
(該類(lèi)是為了讓中間件CTMedator
通過(guò)NSClassFromString
生成類(lèi)),類(lèi)中定義Action_
開(kāi)頭的可調(diào)用的方法(為了讓中間件CTMedator
通過(guò)NSSelectorFromString
生成方法器)假颇,并且這些方法都有一個(gè)字典類(lèi)型參數(shù)接收調(diào)用者傳遞過(guò)來(lái)的信息若债。創(chuàng)建
CTMedator
的分類(lèi)(方便擴(kuò)展、分塊)拆融,此分類(lèi)對(duì)應(yīng)著Target_A
蠢琳,分類(lèi)中定義該組件對(duì)外(調(diào)用者)開(kāi)放的 API 方法,該組件的開(kāi)發(fā)者需要使用CTMedator
的核心方法performTarget:action:params:shouldcacheTarget:
完成方法調(diào)用镜豹。
上層組件
導(dǎo)入對(duì)應(yīng) CTMedator
的分類(lèi)傲须,完成方法調(diào)用。
相比傳統(tǒng)的中介者模式趟脂,這種 target-action
方案解放了中間件對(duì)其他組件的依賴(lài)泰讽,因?yàn)樗峭ㄟ^(guò) NSClassFromString
和 NSSelectorFromString
來(lái)生成類(lèi)的實(shí)例和方法器SEL
的,然后介入消息的分發(fā)機(jī)制完成消息分發(fā)的昔期,即所謂的主動(dòng)發(fā)現(xiàn)服務(wù)已卸。傳統(tǒng)的中介者模式中,中間件和其他組件是雙向依賴(lài)的:
target-action
方式則是單向依賴(lài)硼一,這樣做的一個(gè)好處就是降低了一定的耦合累澡,在我們移除某個(gè)組件時(shí),中間件無(wú)需進(jìn)行改動(dòng)般贼。
那么愧哟,由于沒(méi)有引入具體的類(lèi)奥吩,而是通過(guò)字符生成對(duì)應(yīng)的類(lèi)和方法,那么關(guān)于 CTMedator
的分類(lèi)要清楚的知道 Target_
類(lèi)以及其中的內(nèi)容蕊梧。
CTMedator
的分類(lèi)可以劃分為一個(gè)組件霞赫,必要時(shí),集成到項(xiàng)目中進(jìn)行調(diào)用肥矢。
- 面向接口
Protocol
在路由和 target-action
方案中端衰,都存在硬編碼問(wèn)題、參數(shù)不明確問(wèn)題:URL
甘改、Target_
旅东、 Action_
的硬編碼,參數(shù)都是通過(guò)字典的形式傳遞楼誓,類(lèi)型不明確玉锌。
面向接口 的方式能夠很好的解決這兩個(gè)問(wèn)題。面向接口的方案通常由兩部分組成疟羹,一個(gè)是用來(lái)管理接口協(xié)議的類(lèi)(ModuleManager
)主守,一個(gè)是具體的接口協(xié)議(ComponentProtocol
)。
ModuleManager
負(fù)責(zé)消息的調(diào)用和轉(zhuǎn)發(fā)榄融,它內(nèi)部需要存儲(chǔ)一張映射表参淫,完成 Protocol -> Class
的工作。ComponentProtocol
文件定義了業(yè)務(wù)組件可以提供的功能服務(wù)愧杯,可以將所有服務(wù)都定義到其中涎才,也可以按組件劃分。這樣所有調(diào)用方只需要依賴(lài)中間件力九,不需要依賴(lài)其他的組件耍铜,而中間件通過(guò)接口協(xié)議綁定可以用于服務(wù)的類(lèi),即每個(gè)組件有一個(gè)用于實(shí)現(xiàn)對(duì)外提供的接口協(xié)議的類(lèi)跌前。在編譯時(shí)棕兼,將對(duì)應(yīng)的類(lèi)注冊(cè)到ModuleManager
中,Protocol 的名稱(chēng)即為查找的 key抵乓。
注冊(cè)綁定:
[ModuleManager registerClass:User forProtocol:@protocol(UserProtocol)];
調(diào)用時(shí)通過(guò)接口協(xié)議從 ModuleManager 中映射出注冊(cè)的 Class伴挚,將獲取到的 Class 實(shí)例化,并調(diào)用協(xié)議方法完成服務(wù)調(diào)用灾炭。
Class cls = [[ModuleManager sharedInstance] classForProtocol:@protocol(UserProtocol)];
NSObject <UserProtocol> *user = [[cls alloc] init];
NSString *userName = [user getUserName];
接口協(xié)議的方式雖然可以很好的解決參數(shù)類(lèi)型的不確定性茎芋,硬編碼問(wèn)題(實(shí)現(xiàn)部分可以任意替換),但是它不是前面兩種的替代品蜈出,因?yàn)樗麄兌加凶约旱膫?cè)重點(diǎn)田弥,如 路由URL 可以在應(yīng)用之間實(shí)現(xiàn)消息傳遞,面向接口可以用來(lái)為某類(lèi)添加功能或者對(duì)類(lèi)進(jìn)行功能約束等掏缎。
一些注入框架是支持面向接口的注入的皱蹦,可使用這些庫(kù)取代 ModuleManager
類(lèi)煤杀。
小結(jié)
三種方式都分為底層服務(wù)方和上層使用方眷蜈,服務(wù)方都對(duì)外提供 了服務(wù)媒介沪哺,CTMediator
中是 Target_A
文件,面向接口就是 Protocol
酌儒,路由 URL Scheme
則是回調(diào) block
辜妓。
在三種方式中,個(gè)人覺(jué)得最不推薦的是 CTMediator
方案忌怎,感覺(jué)很是臃腫,雖然可以通過(guò)多個(gè)分類(lèi)去定義組件,但是實(shí)際上對(duì)底層組件的調(diào)用邏輯都耦合在了中間件中酒繁,這意味著中間件需要頻繁的進(jìn)行更新膳凝,另外存在太多的硬編碼地方,target
鸥印、action
以及參數(shù)名都是硬編碼在中間件中的勋功,這樣的方式并不靈活。但是 CTMediator
中通過(guò)運(yùn)行時(shí)解耦了中間件對(duì)底層組件的依賴(lài)库说,以及去 model
化的想法還是非常好的狂鞋。
面向接口 Protocol
的方案貫穿了底層組件、中間件以及上層組件潜的,一方面解耦了中間件對(duì)底層組件的耦合骚揍,底層組件變得透明,可以根據(jù)接口協(xié)議任意替換啰挪,另一方面接口協(xié)議還確定了參數(shù)類(lèi)型信不。但是該方案面向的是應(yīng)用內(nèi)部的功能通信,外部調(diào)用應(yīng)用時(shí)亡呵,還是需要路由或者硬編碼的形式完成抽活。
路由定義了一套用于信息傳遞的標(biāo)準(zhǔn),通過(guò)路由政己,服務(wù)方可以注冊(cè)并實(shí)現(xiàn)符合某種特定條件的服務(wù)酌壕,使用方則通過(guò)中間件傳遞 一條URL
來(lái)調(diào)用該服務(wù)。服務(wù)方和使用方彼此透明歇由,可以任意替換卵牍。和接口協(xié)議比起來(lái),路由的可以處理本地內(nèi)部和遠(yuǎn)程外部的兩種類(lèi)型的調(diào)用沦泌,缺點(diǎn)是 url
需要硬編碼糊昙,而且參數(shù)類(lèi)型都是字符。路由 URL 和接口協(xié)議都需要提前注冊(cè)才能使用谢谦,路由需要 block
释牺,接口協(xié)議需要 class
萝衩。
路由和接口協(xié)議并不沖突,可以使用路由 + 協(xié)議的方式來(lái)實(shí)現(xiàn)中間件没咙,路由實(shí)現(xiàn)外部的調(diào)用猩谊,應(yīng)用的降級(jí)處理等,組件之間通過(guò)接口協(xié)議來(lái)定義功能服務(wù)祭刚,這樣組件內(nèi)部可以在迭代中方便的替換實(shí)現(xiàn)類(lèi)牌捷。
核心工具 CocoaPods
組件化架構(gòu),需要一個(gè)宿主工程涡驮,負(fù)責(zé)集成所有的組件暗甥。每個(gè)組件都是一個(gè)單獨(dú)的工程,通過(guò) Git 私有倉(cāng)庫(kù)來(lái)管理捉捅。這樣拆分工程項(xiàng)目撤防,開(kāi)發(fā)人員只需要關(guān)注與組件相關(guān)的部分,而不用考慮其他組件棒口,新人上手更容易寄月。
所有的組件都上傳到 Git
倉(cāng)庫(kù)并支持 Cocoapods
集成。主工程通過(guò)配置 Podfile
文件陌凳,然后一鍵 pod update
即可剥懒。使用 Cocoapods
來(lái)管理組件主要因?yàn)槠浔旧砉δ軓?qiáng)大,方便的集成整個(gè)項(xiàng)目合敦,解放對(duì)依賴(lài)庫(kù)的管理初橘。使用組件化的集成方式,可以很好的避免傳統(tǒng)項(xiàng)目中的代碼沖突問(wèn)題充岛。
核心命令:
# 安裝命令
sudo gem install cocoapods
# 配置
pod setup
# 通過(guò)Podfile安裝三方庫(kù)
pod install
# 通過(guò)Podfile更新安裝三方庫(kù)
pod update
- Git 常用操作命令 以及開(kāi)發(fā)工具
Git 是一個(gè)分布式版本控制系統(tǒng)保檐,能夠快速高效地處理從小型到大型項(xiàng)目的所有內(nèi)容。Git 官方文獻(xiàn)資料崔梗。
當(dāng)然夜只,如果不想記住這些命令,你可以借助市場(chǎng)上的熱門(mén)開(kāi)發(fā)工具蒜魄,這里推薦 Git 官方桌面端扔亥、Sourcetree。
Xcode 本身就支持項(xiàng)目的 Git 倉(cāng)庫(kù)管理谈为,在 Source control
中就可以創(chuàng)建管理你的項(xiàng)目旅挤。
在你創(chuàng)建項(xiàng)目時(shí),Xcode 就提示你是否創(chuàng)建 Git 倉(cāng)庫(kù):
這里需要注意的就是 podspec
索引文件的編寫(xiě)伞鲫。
Pod::Spec.new do |s|
s.name = '組件工程名'
s.version = '0.0.1'
s.summary = '簡(jiǎn)介'
s.homepage = '遠(yuǎn)程倉(cāng)庫(kù)地址'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { '作者' => '作者' }
s.source = { :git => '遠(yuǎn)程倉(cāng)庫(kù)地址', :tag => s.version }
s.ios.deployment_target = '8.0'
s.source_files = 'Classes/**/*.{swift,h,m,c}'
s.resources = 'Assets/*'
s.dependency 'AFNetworking', '~> 2.3'
s.dependency 'Reachability','~> 3.2
end
source_files:是你要共享的文件
resources:是一些資源文件粘茄,比如圖片資源
dependency:是該組件所需要依賴(lài)的其他組件、三方組件等。
關(guān)于創(chuàng)建子模塊/子文件夾
//簡(jiǎn)單:
subspec 'Twitter' do |sp|
sp.source_files = 'Classes/Twitter' //指定子模塊路徑
end
subspec 'Pinboard' do |sp|
sp.source_files = 'Classes/Pinboard'
end
//復(fù)雜:
Pod::Spec.new do |s|
s.name = 'RestKit'
s.subspec 'Core' do |cs|
cs.dependency 'RestKit/ObjectMapping'
cs.dependency 'RestKit/Network'
cs.dependency 'RestKit/CoreData'
end
s.subspec 'ObjectMapping' do |os|
end
end
更多內(nèi)容參考 基礎(chǔ)-podSpec使用柒瓣。
典型的產(chǎn)品
- 滴滴
滴滴的組件化是將項(xiàng)目拆分為業(yè)務(wù)部分和技術(shù)部分儒搭,業(yè)務(wù)部分包括專(zhuān)車(chē)、拼車(chē)芙贫、巴士等組件搂鲫,使用一個(gè) pods 管理,技術(shù)部分則分為登陸分享屹培、網(wǎng)絡(luò)默穴、緩存等基礎(chǔ)組件怔檩,分別使用不同的 pods 管理褪秀。
組件間的通信通過(guò) ONERouter
中間件進(jìn)行通信,中間件擔(dān)負(fù)協(xié)調(diào)和調(diào)用各個(gè)組件的責(zé)任薛训。組件間通信通過(guò) OpenURL
方法來(lái)進(jìn)行對(duì)應(yīng)的調(diào)用媒吗。ONERouter
內(nèi)部保存一份 Class - URL 的映射表,通過(guò) URL 找到 Class 并發(fā)起調(diào)用乙埃, Class 的注冊(cè)放在 +load
中進(jìn)行闸英。
- 淘寶
淘寶架構(gòu)的核心思想是一切皆組件,將工程中所有代碼都抽象為組件介袜。在 CocoaPods 中可以通過(guò) podfile 很好的配置各個(gè)組件甫何,包括組件的增加和刪除,以及控制某個(gè)組件的版本遇伞。
淘寶架構(gòu)的主要分為四層辙喂,從上到下依次是:業(yè)務(wù)組件 -> 核心層 /容器-> 中間件/功能封裝 -> 底層庫(kù)。容器是整個(gè)架構(gòu)的核心鸠珠,負(fù)責(zé)組件間的調(diào)度和消息派發(fā)巍耗。
總線(xiàn)設(shè)計(jì):URL 路由 + 服務(wù) + 消息。統(tǒng)一所有組件的通信標(biāo)準(zhǔn)渐排,各個(gè)業(yè)務(wù)間通過(guò)總線(xiàn)進(jìn)行通信炬太。
URL 路由
路由 URL 統(tǒng)一對(duì)三端的行為進(jìn)行了統(tǒng)一,一套 URL 就可以調(diào)起 iOS驯耻、Android亲族、前端三個(gè)平臺(tái)的對(duì)應(yīng)組件。
URL 路由請(qǐng)求可以被解析就直接調(diào)起相應(yīng)的組件可缚,如果不能被解析(沒(méi)有對(duì)應(yīng)的組件)就跳轉(zhuǎn) H5 頁(yè)面霎迫,這稱(chēng)為降級(jí)處理。
服務(wù)
服務(wù)提供一些公共服務(wù)城看,是面向接口的女气,通過(guò)接口協(xié)議 Protocol
進(jìn)行調(diào)用。
消息
URL 路由通常都是一對(duì)一進(jìn)行通信测柠,那么針對(duì)一對(duì)多的消息派發(fā)和調(diào)度就可以通過(guò)消息完成炼鞠,這類(lèi)似于 iOS 的通知機(jī)制缘滥。例如應(yīng)用的前后臺(tái)切換、Socket的推送消息等谒主,都可以通過(guò)消息機(jī)制分發(fā)到接收消息的組件朝扼。
小結(jié)
我們可以看到,滴滴和淘寶的組件化上有很多的相似之處霎肯,組件化的核心工具 CocoaPods擎颖,URL 路由進(jìn)行頁(yè)面的路由跳轉(zhuǎn),其他的如接口協(xié)議观游、消息通知等搂捧,應(yīng)該都有類(lèi)似的解決方法。除了管理組件的核心 CocoaPods 工具懂缕,URL 路由允跑、接口協(xié)議服務(wù)、消息通知等都是我們?cè)诮M件化過(guò)程中使用到的利器搪柑。
總結(jié)
組件化開(kāi)發(fā)就是將項(xiàng)目進(jìn)行拆分成一個(gè)個(gè)獨(dú)立的功能組件聋丝,然后將其組合成一個(gè)完整的項(xiàng)目。那么工碾,如何拆分弱睦?組件如何通信?如何組合渊额?都是我們要考慮的問(wèn)題况木。關(guān)于分層和拆分的粒度都沒(méi)有標(biāo)準(zhǔn)化的,需要開(kāi)發(fā)者根據(jù)以往已經(jīng)合理的進(jìn)行規(guī)范端圈。組件間的通信有多種方式焦读,這里比較推崇淘寶的架構(gòu),路由 + 服務(wù) + 消息的形式實(shí)現(xiàn)多種方式的通信舱权。組件化的核心工具就是 CocoaPods
矗晃,我們要做的就是將組件項(xiàng)目上傳到 Git 或者碼云,編寫(xiě)項(xiàng)目的 podSpec
文件讓組件支持 CocoaPods
集成 即可宴倍。CocoaPods
的功能十分強(qiáng)大张症,即使非組件化項(xiàng)目,我們同樣使用它來(lái)管理依賴(lài)庫(kù)鸵贬,安裝俗他、卸載、升級(jí)阔逼、降級(jí)等兆衅,只需要一個(gè)命令即可完成,作為開(kāi)發(fā)者,這個(gè)工具是必定要掌握的羡亩。
參考
實(shí)踐篇
上一章節(jié)中摩疑,我們簡(jiǎn)單介紹了以下組件化的概念、使用到的工具等畏铆,這一章節(jié)中我們來(lái)演示一個(gè)組件如何制作雷袋。
組件的創(chuàng)建
首先我們來(lái)為項(xiàng)目創(chuàng)建一個(gè)關(guān)于網(wǎng)絡(luò)請(qǐng)求的功能組件 LLNetworking
。
- 拉取模版
我們將創(chuàng)建在桌面上的一個(gè)名為 Demo
文件夾中辞居。通過(guò)終端進(jìn)入到該文件夾下楷怒,然后輸入命令:
pod lib create LLNetworking
這個(gè)命令會(huì)為了拉取 Pod 的 基礎(chǔ)模板。拉取之后瓦灶,還會(huì)通過(guò)詢(xún)問(wèn)的形式為你配置一些東西:
// 作用的平臺(tái)
What platform do you want to use?? [ iOS / macOS ]
> iOS
// 語(yǔ)言環(huán)境
What language do you want to use?? [ Swift / ObjC ]
> ObjC
// 是否需要一個(gè) demo 用來(lái)測(cè)試組件
Would you like to include a demo application with your library? [ Yes / No ]
> Yes
Which testing frameworks will you use? [ Specta / Kiwi / None ]
> None
Would you like to do view based testing? [ Yes / No ]
> Yes
// 組件中鸠删,文件的前綴
What is your class prefix?
> LL
確認(rèn)之后,系統(tǒng)會(huì)為你自動(dòng)配置組件項(xiàng)目倚搬,創(chuàng)建好的項(xiàng)目如下:
- Example 工程
項(xiàng)目文件目錄中存在一個(gè)名為 Example
的工程冶共,這個(gè)工程是你選擇 Would you like to include a demo application with your library?
中選擇 Yes
時(shí)為你添加的,這個(gè)還是很有用的每界,在你開(kāi)發(fā)過(guò)程中可以通過(guò)它來(lái)集成測(cè)試組件功能的正確性、完整性家卖。 我們先打開(kāi)這個(gè) Example
來(lái)看下:
這個(gè) Example
已經(jīng)為你的組件創(chuàng)建了索引文件 podspec
眨层,并且集成了該組件。我們來(lái)看下 Example
的 Podfile
的內(nèi)容:
use_frameworks!
platform :ios, '8.0'
target 'LLNetworking_Example' do
pod 'LLNetworking', :path => '../'
target 'LLNetworking_Tests' do
inherit! :search_paths
pod 'FBSnapshotTestCase'
end
end
其中為你集成了一個(gè)測(cè)試用例 pod 'FBSnapshotTestCase'
上荡,目前可以忽略趴樱。
我們可以看到: pod 'LLNetworking', :path => '../'
這一句,path
路徑指向了本地路徑酪捡,對(duì)應(yīng) LLNetworking
主目錄下:
這個(gè)文件夾下叁征,一個(gè)存放你的各種類(lèi)文件,一個(gè)存放圖片資源等逛薇。
- podspec 文件
在你回答之前問(wèn)題之后捺疼,pod 為你自動(dòng)創(chuàng)建了該文件,并執(zhí)行了 pod install
命令永罚,該命令會(huì)找到組件的索引文件(也在本地) LLNetworking.podspec
:
#
# Be sure to run `pod lib lint LLNetworking.podspec' to ensure this is a
# valid spec before submitting.
#
# Any lines starting with a # are optional, but their use is encouraged
# To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html
#
Pod::Spec.new do |s|
s.name = 'LLNetworking'
s.version = '0.1.0'
s.summary = 'A short description of LLNetworking.'
# This description is used to generate tags and improve search results.
# * Think: What does it do? Why did you write it? What is the focus?
# * Try to keep it short, snappy and to the point.
# * Write the description between the DESC delimiters below.
# * Finally, don't worry about the indent, CocoaPods strips it!
s.description = <<-DESC
TODO: Add long description of the pod here.
DESC
s.homepage = 'https://github.com/LOLITA0164/LLNetworking'
# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'LOLITA0164' => '476512340@qq.com' }
s.source = { :git => 'https://github.com/LOLITA0164/LLNetworking.git', :tag => s.version.to_s }
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'
s.ios.deployment_target = '8.0'
s.source_files = 'LLNetworking/Classes/**/*'
# s.resource_bundles = {
# 'LLNetworking' => ['LLNetworking/Assets/*.png']
# }
# s.public_header_files = 'Pod/Classes/**/*.h'
# s.frameworks = 'UIKit', 'MapKit'
# s.dependency 'AFNetworking', '~> 2.3'
end
該文件為你的組件自動(dòng)配置了一些基本的信息啤呼,因?yàn)槲抑笆褂眠^(guò) trunk 登陸過(guò),所以這里有的的賬號(hào)信息呢袱。當(dāng)然這些信息是需要你根據(jù)情況修改的官扣,更多的配置你可以搜索相關(guān)文檔。
注意:這里的 Git 地址目前是找不到的羞福,后期需要自己關(guān)聯(lián)惕蹄。
設(shè)置共享文件
podspec
文件中 s.source_files = 'LLNetworking/Classes/**/*'
指代共享的資源路徑,我們需要將共享的文件放到這里來(lái)。
我們打開(kāi)組件的目錄查看卖陵,可以看到這里已經(jīng)有了名為 ReplaceMe
的文件了恋昼,這暗示你用共享文件替換它。
podspec
文件中還有一個(gè)被注釋掉的:
# s.resource_bundles = {
# 'LLNetworking' => ['LLNetworking/Assets/*.png']
# }
這個(gè)目錄中存放一些圖片等資源赶促,當(dāng)你需要的時(shí)候可以開(kāi)啟來(lái)液肌。
我們來(lái)創(chuàng)建一個(gè) LLNetworking 類(lèi):
@interface LLNetworking : NSObject
-(NSString*)getSomething;
@end
@implementation LLNetworking
-(NSString *)getSomething{
return @"test method.";
}
@end
將其移動(dòng)到組件的共享目錄下并刪除掉空文件ReplaceMe
:
這樣,我們就設(shè)置好了共享的內(nèi)容鸥滨,即組件開(kāi)發(fā)好了嗦哆。接下來(lái),我們使用 Example
工程來(lái)使用這個(gè)組件的內(nèi)容婿滓。
終端進(jìn)入 Example
工程目錄下老速,執(zhí)行 pod install
命令來(lái)安裝組件。
我們發(fā)現(xiàn)凸主,Example
項(xiàng)目中 Pods/Development Pods/LLNetworking
下橘券,多出來(lái)最新添加的文件。
使用組件
我們安裝好組件之后來(lái)使用一下組件的功能卿吐,就像使用三方庫(kù)那樣:
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
LLNetworking * networking = LLNetworking.new;
NSLog(@"%@",networking.getSomething);
}
控制臺(tái)輸出:
2019-11-08 17:14:47.455341+0800 LLNetworking_Example[7038:1682304] test method.
這表示功能正常旁舰。
在組件開(kāi)發(fā)過(guò)程中,使用 pod 'LLNetworking', :path => '../' 將路徑指向本地是很有必要的嗡官,方便測(cè)試你的組件配置是否正確箭窜,功能是否完善,相比推到遠(yuǎn)程衍腥、發(fā)布再集成磺樱,這方便太多了。
三方依賴(lài)庫(kù)
有時(shí)候婆咸,我們的組件還依賴(lài)其他的組件竹捉,又或者是三方庫(kù)。我們通過(guò) s.dependency 字段去設(shè)置尚骄,多個(gè)庫(kù)可以分開(kāi)寫(xiě)多次块差。
在 Podfiles 模版里最后一條已經(jīng)為我們添加好了,所依賴(lài)的是 AFNetworking 乖仇,正好是我們網(wǎng)絡(luò)請(qǐng)求組件所依賴(lài)的憾儒,我們把它開(kāi)啟,重新 pod install :
Analyzing dependencies
Fetching podspec for `LLNetworking` from `../`
Downloading dependencies
Installing AFNetworking (2.7.0)
……
我們發(fā)現(xiàn)乃沙,Example
自動(dòng)拉取了組件 LLNetworking
所依賴(lài)的其他組件起趾。CocoaPods
工具的另外一個(gè)優(yōu)點(diǎn)就是,多個(gè)組件依賴(lài)同一個(gè)組件時(shí)警儒,它會(huì)自動(dòng)幫你檢測(cè)安裝训裆,而不會(huì)重復(fù)導(dǎo)入眶根。
我們發(fā)現(xiàn) Example
工程的 Pods
中,本地開(kāi)發(fā)的組件和遠(yuǎn)程發(fā)布的組件被分別放在了不同的目錄下边琉。
有了 AFNetworking
之后属百,你就可以修改你的網(wǎng)絡(luò)請(qǐng)求組件了:
#import <AFNetworking/AFNetworking.h>
@interface LLNetworking : NSObject
@property(strong,nonatomic)NSURLSessionDataTask *task;
- (NSURLSessionDataTask *)POSTWithURLString:(NSString *)URLString parameters:(id)parameters success:(void (^)(id responseObject))success failure:(void (^)(id error))failure;
@end
@implementation LLNetworking
- (NSURLSessionDataTask *)POSTWithURLString:(NSString *)URLString parameters:(id)parameters success:(void (^)(id responseObject))success failure:(void (^)(id error))failure{
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
manager.requestSerializer.timeoutInterval = 20;
_task = [manager POST:URLString parameters:parameters success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
if (success) {
success(@{@"status":@"success"});
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
if (failure) {
failure(@{@"status":@"failure"});
}
}];
return _task;
}
@end
修改好之后,還不能直接在 Example 中使用变姨,需要卸載組件再重新安裝族扰。注釋掉 pod 'LLNetworking', :path => '../' 之后執(zhí)行 pod install 即可完成卸載。
至此定欧,你完成了組件的創(chuàng)建渔呵、文件共享、本地化測(cè)試使用和更新砍鸠。但是扩氢,我們的組件畢竟是要服務(wù)于宿主工程的,如果僅僅只能是通過(guò)本地集成爷辱,那意義不大录豺,我們要將其關(guān)聯(lián)到遠(yuǎn)程服務(wù)器,推送到本地搭建的 GitLab饭弓,又或者是 GitHub双饥、碼云、Coding 等平臺(tái)示启。
關(guān)聯(lián)遠(yuǎn)程倉(cāng)庫(kù)
在模版 podspec 文件中兢哭,已經(jīng)幫我們指定了一個(gè) GitHub 的倉(cāng)庫(kù)地址,
s.homepage = 'https://github.com/LOLITA0164/LLNetworking'
你可以使用它或者進(jìn)行修改它夫嗓。我們這里選擇使用它,先去 GitHub
創(chuàng)建對(duì)應(yīng)的倉(cāng)庫(kù)冲秽。
在最初創(chuàng)建組件時(shí)舍咖,系統(tǒng)已經(jīng)幫我們創(chuàng)建好了本地 Git 倉(cāng)庫(kù),進(jìn)入到項(xiàng)目中锉桑,顯示出隱藏文件夾就可以看到(command+shift+. 顯隱):
如果沒(méi)有排霉,你可以使用命令 git init
創(chuàng)建一個(gè)。現(xiàn)在民轴,我們要將之前的修改進(jìn)行提交(本地提交)攻柠。
git commit -am "第一次提交"
然后我們要把本地的 Git 倉(cāng)庫(kù)和剛剛創(chuàng)建的遠(yuǎn)程倉(cāng)庫(kù)進(jìn)行關(guān)聯(lián)。如何關(guān)聯(lián)呢后裸?你在網(wǎng)站上創(chuàng)建項(xiàng)目后有了這樣的提示:
這里有三種:創(chuàng)建一個(gè)新的倉(cāng)庫(kù)瑰钮,推送一個(gè)已存在的倉(cāng)庫(kù)以及從其他倉(cāng)庫(kù)導(dǎo)入。我們這里使用第二種即可微驶。
添加遠(yuǎn)程倉(cāng)庫(kù):
git remote add origin https://github.com/LOLITA0164/LLNetworking.git
將本地內(nèi)容推送到遠(yuǎn)程倉(cāng)庫(kù):
git push -u origin master
可能會(huì)出現(xiàn)讓你登陸驗(yàn)證浪谴,輸入你的用戶(hù)名和密碼即可开睡。出現(xiàn)以下信息即表示推送成功。
remote: Resolving deltas: 100% (49/49), done.
To https://github.com/LOLITA0164/LLNetworking.git
* [new branch] master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.
回到 GitHub 刷新一下即可看到你的提交記錄苟耻。
上述是通過(guò)終端命令進(jìn)行 git 操作篇恒,如果你并不熟悉 git 命令,你大可以使用便捷的可視化工具(上一章節(jié)有所提及)凶杖,僅需簡(jiǎn)單的點(diǎn)擊操作即可完成項(xiàng)目的管理胁艰。
打 tag 并發(fā)布到 Cocoapods
打標(biāo)簽
至此,我們已經(jīng)成功的將本地倉(cāng)庫(kù)關(guān)聯(lián)并推送到遠(yuǎn)程倉(cāng)庫(kù)智蝠,現(xiàn)在我們要發(fā)布一個(gè)可用的組件腾么。
首先我們要給當(dāng)前項(xiàng)目打一個(gè) tag 版本號(hào),在 podspec 中:
s.version = '0.1.0'
指定的版本號(hào)是 0.1.0寻咒,那么我們就同樣打個(gè) 0.1.0 的 tag:
$ git tag 0.1.0 // 打 tag
$ git push --tags // 推送到遠(yuǎn)程
打 tag
默認(rèn)在當(dāng)前分支上哮翘,因?yàn)檫@里只有 master
,所以不用切換分支毛秘,如果后期有其他分支饭寺,注意別弄錯(cuò)了。
刷新頁(yè)面叫挟,項(xiàng)目的 release
選項(xiàng)中會(huì)出現(xiàn)剛剛打的版本艰匙。
你也可以直接在頁(yè)面的 release
下添加新的 tag
,點(diǎn)擊 release
可以看到編輯頁(yè)面:
發(fā)布到 CocoaPods
由于我們創(chuàng)建的項(xiàng)目以及標(biāo)簽的版本號(hào)都是沿用了 podspec
文件中的信息抹恳,因此可以直接驗(yàn)證 podspec
文件信息是否可以通過(guò)驗(yàn)證员凝,如果需要調(diào)整,調(diào)整之后最好同樣先驗(yàn)證:
pod spec lint
podspec
文件的版本號(hào)一定要和tag
保持一致奋献。
如果通過(guò)驗(yàn)證健霹,那么你會(huì)看到類(lèi)似下面的提示,綠色的 passed validation
:
現(xiàn)在可以將 podspec
文件提交到 CocoaPods
上了:
首先要通過(guò) trunk
注冊(cè)生成一條會(huì)話(huà):
// pod trunk register 郵箱 用戶(hù)名 描述
pod trunk register 476512340@qq.com LOLITA0164 --description=組件化demo
然后去郵箱進(jìn)行驗(yàn)證瓶蚂,驗(yàn)證成功會(huì)出現(xiàn)下面頁(yè)面:
現(xiàn)在糖埋,就可以將 podspec
提交給 CocoaPods
了。這個(gè)文件將是別人搜索你的組件的索引窃这。
pod trunk push LLNetworking.podspec --allow-warnings
上傳完成之后瞳别,接可以通過(guò) pod search LLNetworking 搜索到自己的組件了,如果搜索不到杭攻,刪除本地的搜索文件祟敛,命令 :
rm ~/Library/Caches/CocoaPods/search_index.json
重新 search
產(chǎn)生新的搜索文件。
發(fā)布新版本則需要打新的
tag
兆解,重新編輯podspec
文件馆铁,然后再次提交給CocoaPods
。
集成到宿主工程
我們已經(jīng)完成了網(wǎng)絡(luò)組件的創(chuàng)建和發(fā)布痪宰,也支持了 CocoaPods
的集成〉鸺埽現(xiàn)在我們需要將該組件集成到宿主工程中去畔裕,這部分沒(méi)什么好提的,因?yàn)槭褂梅绞胶图扇綆?kù)是一樣的乖订,可以說(shuō)三方庫(kù)只不過(guò)是他人編寫(xiě)的功能組件而已扮饶,我們的組件同樣可以提供給小組成員使用,相比于純粹的三方庫(kù)乍构,我們的許多組件都關(guān)聯(lián)了業(yè)務(wù)部分甜无,或者基于私人的其他組件,因此適用范圍較小哥遮。
小結(jié)
本章節(jié)先介紹了如何通過(guò) pod 的模版工程創(chuàng)建組件岂丘,組件的配置,集成本地組件眠饮,然后介紹了遠(yuǎn)程倉(cāng)庫(kù)的關(guān)聯(lián)奥帘,支持 CocoaPods
的集成等內(nèi)容,學(xué)會(huì)了這些仪召,你就可以將自己得意的功能庫(kù)提供給他人使用寨蹋。在組件化的過(guò)程中,Git
是我們必須要掌握的扔茅,即使你不會(huì)使用命令已旧,但是一定要熟悉相關(guān)的軟件。