理論篇
什么是組件化
組件化開發(fā)就是將一個臃腫的、單一的項(xiàng)目,根據(jù)功能/業(yè)務(wù)/技術(shù)等等進(jìn)行拆分,形成一個個獨(dú)立的功能組件,然后借助 CocoaPods 管理工具將其任意組合,集成一個完整的項(xiàng)目。
你可以將 AFNetworking、SDWebImage 等等三方庫理解為自己項(xiàng)目的一部分,屬于基礎(chǔ)組件部分,而我們要做的就是將項(xiàng)目劃分成多個獨(dú)立功能模塊,再集成為一個完整的項(xiàng)目。這一過程看似多此一舉,但是帶來的優(yōu)勢卻是非常大。
為什么需要組件化
項(xiàng)目初期,功能相對簡單,普通的MVC+模塊文件分割就可以滿足絕大部分的需求。但是隨著功能需求越來越多,業(yè)務(wù)越來越復(fù)雜多樣,現(xiàn)有的架構(gòu)已經(jīng)不太適用了,即使使用了 Git 分支管理,依然經(jīng)常發(fā)生合并沖突等等問題,另外后期的維護(hù)成本也大大增加,業(yè)務(wù)邏輯變得復(fù)雜、模塊之間耦合度很大、查找問題效率變低、項(xiàng)目編譯過程過慢…… 而且伴隨著開發(fā)人員的增多(多個小組之間協(xié)作開發(fā)),這些問題尤為突出,優(yōu)化開發(fā)結(jié)構(gòu)變得非常重要。
中間層
針對上面的問題,第一個想到的優(yōu)化就是新增一個中間層來協(xié)調(diào)各個模塊之間的調(diào)用,所有的模塊都通過這個中間層去實(shí)現(xiàn)調(diào)用和交互,但是這樣雖然一定程度上降低了模塊與模塊的之間的耦合度,但是耦合都轉(zhuǎn)嫁到了中間層上了,并且中間層的改動只能由一個人操作,否則非常容易發(fā)生沖突,本質(zhì)上并沒有發(fā)生多少變化。另外一點(diǎn),查找問題的效率低下、編譯過慢等問題依舊沒有得到有效的解決。
這是傳統(tǒng)的中介者模式,這個中間層會依賴其他組件,其他組件也會依賴中間層完成服務(wù)。
組件化
組件化能夠幫助我們將過大的項(xiàng)目拆解成數(shù)個小組件,開發(fā)者只需要關(guān)注于組件所依賴的其他組件,而無需關(guān)心完整項(xiàng)目的其他部分,每個組件可以自己采取所習(xí)慣的架構(gòu)模式:MVC、MVVM、MVCS等等,就像開發(fā)一款個人獨(dú)立的app那樣自由。
項(xiàng)目組件化之后所帶來的好處是非常多的,我們先總結(jié)一下非組件化所造成的問題:
非組件化:
- 代碼高耦合度、高依賴
- 項(xiàng)目復(fù)雜、臃腫、編譯過長(影響調(diào)試)
- 難以融合/集成其他產(chǎn)品
- 需要統(tǒng)一架構(gòu)
……
組件化:
- 代碼復(fù)用性提高,可方便的集成到其他項(xiàng)目
- 項(xiàng)目可配置,方便集成和功能回退(指定版本)
- 化整為零,將項(xiàng)目細(xì)小化
- 方便組件的并行開發(fā)
- 可方便做單元測試
- 組件自由度高,即插即用
……
當(dāng)然組件化也有著它的缺點(diǎn),對已有的項(xiàng)目實(shí)施組件化架構(gòu)比較困難,耗費(fèi)時間長,項(xiàng)目組成員需要一定學(xué)習(xí)成本;組件化并沒有相應(yīng)的標(biāo)準(zhǔn),拆分的粒度要適中,拆分粒度過高,則讓項(xiàng)目變得復(fù)雜,起到了反作用效果,反之,粒度過低,體現(xiàn)不了組件化的優(yōu)勢,在項(xiàng)目業(yè)務(wù)不斷地添加的過程中,進(jìn)行不斷的嘗試調(diào)整,找到適合自己項(xiàng)目的才是最好的。
組件化的分層
項(xiàng)目組件化中,最難把握的就是粒度問題,這需要開發(fā)的自己的經(jīng)驗(yàn)去把控。這里只給出個人認(rèn)為的層次的劃分。
【基礎(chǔ)組件】:宏定義/常量/自定義工具類,如常用的自定義分類
【功能組件】:項(xiàng)目中所用到的功能,如地圖定位/消息推送/分享等
【業(yè)務(wù)組件】:項(xiàng)目中的模塊/業(yè)務(wù),如聊天室/直播間/個人中心等
【中間組件】:負(fù)責(zé)項(xiàng)目中的路由/消息通知/傳參/回調(diào)等
【宿主工程】:項(xiàng)目容器,用來集成組件,調(diào)整各個組件之間的消息傳遞的容器。
中間層的幾種方案
在組件化過程中,中間層是各個組件的通信的橋梁,中間層在組件化過程中扮演著非常重要的角色。目前關(guān)于中間層的設(shè)計筆者已知的有以下三種方式:基于URL Scheme的路由、基于Runtime的target-action、面向接口。
- 路由
iOS 中支持的 URL Scheme 讓我們能夠在應(yīng)用之間、應(yīng)用內(nèi)部傳遞消息。日常開發(fā)過程中經(jīng)常用到的就是調(diào)用系統(tǒng)服務(wù)、喚起三方app等等,這些屬于應(yīng)用之間的消息傳遞,而我們這里借助 URL Scheme 完成應(yīng)用內(nèi)部的消息傳遞。這里的路由 URL 遵循網(wǎng)上通用的資源標(biāo)識符合 URI,如:appscheme://home/scan?param=value,我們通過 URL 來傳遞信息,下層服務(wù)方通過 URL 獲取參數(shù)提供服務(wù),上層消費(fèi)者通過 URL 獲取到服務(wù),完成調(diào)用。
基于 URL Scheme 的三方庫
JLRoutes 是一種基于 URL Scheme 的路由框架,它全局會保存一個Map,key 是路由協(xié)議 url,value 則是對 url 解析后 block 回調(diào),你可以在該回調(diào)中處理具體的業(yè)務(wù)。
實(shí)例:
例如我們的路由協(xié)議定義如下:
scheme://描述/打開方式/保留字段/功能標(biāo)識?參數(shù)1=值1&參數(shù)2=值2
||
myroute://market/1/route/cjpm?stockcode=600212.ss
首先配置路由 url 和 對應(yīng)的回調(diào)處理:
/// 默認(rèn)下都會進(jìn)入這里,這里填寫路由匹配規(guī)則
[JLRoutes.globalRoutes addRoute:@"/market/:operate/route/:code" handler:^BOOL(NSDictionary<NSString *,id> * _Nonnull parameters) {
NSLog(@"%@", parameters);
// 接下來的業(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 的提前注冊、實(shí)現(xiàn)服務(wù),CTMediator 借助 OC 運(yùn)行時的特性,現(xiàn)實(shí)組件之間服務(wù)的自動發(fā)現(xiàn),無需提前注冊即可實(shí)現(xiàn)組件間的調(diào)用,因此,這種方案的可維護(hù)性、可讀性、擴(kuò)展性相對較高。
官方的 Demo 中,結(jié)構(gòu)是這樣的:

CTMediator 的使用流程大體是這樣的:
底層組件
創(chuàng)建
Target_開頭的目標(biāo)類,如Target_A(該類是為了讓中間件CTMedator通過NSClassFromString生成類),類中定義Action_開頭的可調(diào)用的方法(為了讓中間件CTMedator通過NSSelectorFromString生成方法器),并且這些方法都有一個字典類型參數(shù)接收調(diào)用者傳遞過來的信息。創(chuàng)建
CTMedator的分類(方便擴(kuò)展、分塊),此分類對應(yīng)著Target_A,分類中定義該組件對外(調(diào)用者)開放的 API 方法,該組件的開發(fā)者需要使用CTMedator的核心方法performTarget:action:params:shouldcacheTarget:完成方法調(diào)用。
上層組件
導(dǎo)入對應(yīng) CTMedator 的分類,完成方法調(diào)用。
相比傳統(tǒng)的中介者模式,這種 target-action 方案解放了中間件對其他組件的依賴,因?yàn)樗峭ㄟ^ NSClassFromString 和 NSSelectorFromString 來生成類的實(shí)例和方法器SEL的,然后介入消息的分發(fā)機(jī)制完成消息分發(fā)的,即所謂的主動發(fā)現(xiàn)服務(wù)。傳統(tǒng)的中介者模式中,中間件和其他組件是雙向依賴的:

target-action 方式則是單向依賴,這樣做的一個好處就是降低了一定的耦合,在我們移除某個組件時,中間件無需進(jìn)行改動。

那么,由于沒有引入具體的類,而是通過字符生成對應(yīng)的類和方法,那么關(guān)于 CTMedator 的分類要清楚的知道 Target_ 類以及其中的內(nèi)容。
CTMedator 的分類可以劃分為一個組件,必要時,集成到項(xiàng)目中進(jìn)行調(diào)用。
- 面向接口
Protocol
在路由和 target-action 方案中,都存在硬編碼問題、參數(shù)不明確問題:URL 、Target_ 、 Action_ 的硬編碼,參數(shù)都是通過字典的形式傳遞,類型不明確。
面向接口 的方式能夠很好的解決這兩個問題。面向接口的方案通常由兩部分組成,一個是用來管理接口協(xié)議的類(ModuleManager),一個是具體的接口協(xié)議(ComponentProtocol)。
ModuleManager 負(fù)責(zé)消息的調(diào)用和轉(zhuǎn)發(fā),它內(nèi)部需要存儲一張映射表,完成 Protocol -> Class 的工作。ComponentProtocol 文件定義了業(yè)務(wù)組件可以提供的功能服務(wù),可以將所有服務(wù)都定義到其中,也可以按組件劃分。這樣所有調(diào)用方只需要依賴中間件,不需要依賴其他的組件,而中間件通過接口協(xié)議綁定可以用于服務(wù)的類,即每個組件有一個用于實(shí)現(xiàn)對外提供的接口協(xié)議的類。在編譯時,將對應(yīng)的類注冊到ModuleManager 中,Protocol 的名稱即為查找的 key。
注冊綁定:
[ModuleManager registerClass:User forProtocol:@protocol(UserProtocol)];
調(diào)用時通過接口協(xié)議從 ModuleManager 中映射出注冊的 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ù)類型的不確定性,硬編碼問題(實(shí)現(xiàn)部分可以任意替換),但是它不是前面兩種的替代品,因?yàn)樗麄兌加凶约旱膫?cè)重點(diǎn),如 路由URL 可以在應(yīng)用之間實(shí)現(xiàn)消息傳遞,面向接口可以用來為某類添加功能或者對類進(jìn)行功能約束等。
一些注入框架是支持面向接口的注入的,可使用這些庫取代 ModuleManager 類。
小結(jié)
三種方式都分為底層服務(wù)方和上層使用方,服務(wù)方都對外提供 了服務(wù)媒介,CTMediator 中是 Target_A 文件,面向接口就是 Protocol,路由 URL Scheme 則是回調(diào) block。
在三種方式中,個人覺得最不推薦的是 CTMediator 方案,感覺很是臃腫,雖然可以通過多個分類去定義組件,但是實(shí)際上對底層組件的調(diào)用邏輯都耦合在了中間件中,這意味著中間件需要頻繁的進(jìn)行更新,另外存在太多的硬編碼地方,target、action以及參數(shù)名都是硬編碼在中間件中的,這樣的方式并不靈活。但是 CTMediator 中通過運(yùn)行時解耦了中間件對底層組件的依賴,以及去 model 化的想法還是非常好的。
面向接口 Protocol 的方案貫穿了底層組件、中間件以及上層組件,一方面解耦了中間件對底層組件的耦合,底層組件變得透明,可以根據(jù)接口協(xié)議任意替換,另一方面接口協(xié)議還確定了參數(shù)類型。但是該方案面向的是應(yīng)用內(nèi)部的功能通信,外部調(diào)用應(yīng)用時,還是需要路由或者硬編碼的形式完成。
路由定義了一套用于信息傳遞的標(biāo)準(zhǔn),通過路由,服務(wù)方可以注冊并實(shí)現(xiàn)符合某種特定條件的服務(wù),使用方則通過中間件傳遞 一條URL 來調(diào)用該服務(wù)。服務(wù)方和使用方彼此透明,可以任意替換。和接口協(xié)議比起來,路由的可以處理本地內(nèi)部和遠(yuǎn)程外部的兩種類型的調(diào)用,缺點(diǎn)是 url 需要硬編碼,而且參數(shù)類型都是字符。路由 URL 和接口協(xié)議都需要提前注冊才能使用,路由需要 block,接口協(xié)議需要 class。
路由和接口協(xié)議并不沖突,可以使用路由 + 協(xié)議的方式來實(shí)現(xiàn)中間件,路由實(shí)現(xiàn)外部的調(diào)用,應(yīng)用的降級處理等,組件之間通過接口協(xié)議來定義功能服務(wù),這樣組件內(nèi)部可以在迭代中方便的替換實(shí)現(xiàn)類。
核心工具 CocoaPods
組件化架構(gòu),需要一個宿主工程,負(fù)責(zé)集成所有的組件。每個組件都是一個單獨(dú)的工程,通過 Git 私有倉庫來管理。這樣拆分工程項(xiàng)目,開發(fā)人員只需要關(guān)注與組件相關(guān)的部分,而不用考慮其他組件,新人上手更容易。

所有的組件都上傳到 Git 倉庫并支持 Cocoapods 集成。主工程通過配置 Podfile 文件,然后一鍵 pod update 即可。使用 Cocoapods 來管理組件主要因?yàn)槠浔旧砉δ軓?qiáng)大,方便的集成整個項(xiàng)目,解放對依賴庫的管理。使用組件化的集成方式,可以很好的避免傳統(tǒng)項(xiàng)目中的代碼沖突問題。
核心命令:
# 安裝命令
sudo gem install cocoapods
# 配置
pod setup
# 通過Podfile安裝三方庫
pod install
# 通過Podfile更新安裝三方庫
pod update
- Git 常用操作命令 以及開發(fā)工具
Git 是一個分布式版本控制系統(tǒng),能夠快速高效地處理從小型到大型項(xiàng)目的所有內(nèi)容。Git 官方文獻(xiàn)資料。
當(dāng)然,如果不想記住這些命令,你可以借助市場上的熱門開發(fā)工具,這里推薦 Git 官方桌面端、Sourcetree。
Xcode 本身就支持項(xiàng)目的 Git 倉庫管理,在 Source control 中就可以創(chuàng)建管理你的項(xiàng)目。

在你創(chuàng)建項(xiàng)目時,Xcode 就提示你是否創(chuàng)建 Git 倉庫:

這里需要注意的就是 podspec 索引文件的編寫。
Pod::Spec.new do |s|
s.name = '組件工程名'
s.version = '0.0.1'
s.summary = '簡介'
s.homepage = '遠(yuǎn)程倉庫地址'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { '作者' => '作者' }
s.source = { :git => '遠(yuǎn)程倉庫地址', :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:是該組件所需要依賴的其他組件、三方組件等。
關(guān)于創(chuàng)建子模塊/子文件夾
//簡單:
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ù)部分包括專車、拼車、巴士等組件,使用一個 pods 管理,技術(shù)部分則分為登陸分享、網(wǎng)絡(luò)、緩存等基礎(chǔ)組件,分別使用不同的 pods 管理。
組件間的通信通過 ONERouter 中間件進(jìn)行通信,中間件擔(dān)負(fù)協(xié)調(diào)和調(diào)用各個組件的責(zé)任。組件間通信通過 OpenURL 方法來進(jìn)行對應(yīng)的調(diào)用。ONERouter 內(nèi)部保存一份 Class - URL 的映射表,通過 URL 找到 Class 并發(fā)起調(diào)用, Class 的注冊放在 +load 中進(jìn)行。
- 淘寶
淘寶架構(gòu)的核心思想是一切皆組件,將工程中所有代碼都抽象為組件。在 CocoaPods 中可以通過 podfile 很好的配置各個組件,包括組件的增加和刪除,以及控制某個組件的版本。
淘寶架構(gòu)的主要分為四層,從上到下依次是:業(yè)務(wù)組件 -> 核心層 /容器-> 中間件/功能封裝 -> 底層庫。容器是整個架構(gòu)的核心,負(fù)責(zé)組件間的調(diào)度和消息派發(fā)。
總線設(shè)計:URL 路由 + 服務(wù) + 消息。統(tǒng)一所有組件的通信標(biāo)準(zhǔn),各個業(yè)務(wù)間通過總線進(jìn)行通信。
URL 路由
路由 URL 統(tǒng)一對三端的行為進(jìn)行了統(tǒng)一,一套 URL 就可以調(diào)起 iOS、Android、前端三個平臺的對應(yīng)組件。
URL 路由請求可以被解析就直接調(diào)起相應(yīng)的組件,如果不能被解析(沒有對應(yīng)的組件)就跳轉(zhuǎn) H5 頁面,這稱為降級處理。
服務(wù)
服務(wù)提供一些公共服務(wù),是面向接口的,通過接口協(xié)議 Protocol 進(jìn)行調(diào)用。
消息
URL 路由通常都是一對一進(jìn)行通信,那么針對一對多的消息派發(fā)和調(diào)度就可以通過消息完成,這類似于 iOS 的通知機(jī)制。例如應(yīng)用的前后臺切換、Socket的推送消息等,都可以通過消息機(jī)制分發(fā)到接收消息的組件。
小結(jié)
我們可以看到,滴滴和淘寶的組件化上有很多的相似之處,組件化的核心工具 CocoaPods,URL 路由進(jìn)行頁面的路由跳轉(zhuǎn),其他的如接口協(xié)議、消息通知等,應(yīng)該都有類似的解決方法。除了管理組件的核心 CocoaPods 工具,URL 路由、接口協(xié)議服務(wù)、消息通知等都是我們在組件化過程中使用到的利器。
總結(jié)
組件化開發(fā)就是將項(xiàng)目進(jìn)行拆分成一個個獨(dú)立的功能組件,然后將其組合成一個完整的項(xiàng)目。那么,如何拆分?組件如何通信?如何組合?都是我們要考慮的問題。關(guān)于分層和拆分的粒度都沒有標(biāo)準(zhǔn)化的,需要開發(fā)者根據(jù)以往已經(jīng)合理的進(jìn)行規(guī)范。組件間的通信有多種方式,這里比較推崇淘寶的架構(gòu),路由 + 服務(wù) + 消息的形式實(shí)現(xiàn)多種方式的通信。組件化的核心工具就是 CocoaPods ,我們要做的就是將組件項(xiàng)目上傳到 Git 或者碼云,編寫項(xiàng)目的 podSpec 文件讓組件支持 CocoaPods 集成 即可。CocoaPods 的功能十分強(qiáng)大,即使非組件化項(xiàng)目,我們同樣使用它來管理依賴庫,安裝、卸載、升級、降級等,只需要一個命令即可完成,作為開發(fā)者,這個工具是必定要掌握的。
參考
實(shí)踐篇
上一章節(jié)中,我們簡單介紹了以下組件化的概念、使用到的工具等,這一章節(jié)中我們來演示一個組件如何制作。
組件的創(chuàng)建
首先我們來為項(xiàng)目創(chuàng)建一個關(guān)于網(wǎng)絡(luò)請求的功能組件 LLNetworking。
- 拉取模版
我們將創(chuàng)建在桌面上的一個名為 Demo 文件夾中。通過終端進(jìn)入到該文件夾下,然后輸入命令:
pod lib create LLNetworking
這個命令會為了拉取 Pod 的 基礎(chǔ)模板。拉取之后,還會通過詢問的形式為你配置一些東西:
// 作用的平臺
What platform do you want to use?? [ iOS / macOS ]
> iOS
// 語言環(huán)境
What language do you want to use?? [ Swift / ObjC ]
> ObjC
// 是否需要一個 demo 用來測試組件
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)會為你自動配置組件項(xiàng)目,創(chuàng)建好的項(xiàng)目如下:

- Example 工程
項(xiàng)目文件目錄中存在一個名為 Example 的工程,這個工程是你選擇 Would you like to include a demo application with your library? 中選擇 Yes 時為你添加的,這個還是很有用的,在你開發(fā)過程中可以通過它來集成測試組件功能的正確性、完整性。 我們先打開這個 Example 來看下:

這個 Example 已經(jīng)為你的組件創(chuàng)建了索引文件 podspec,并且集成了該組件。我們來看下 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
其中為你集成了一個測試用例 pod 'FBSnapshotTestCase',目前可以忽略。
我們可以看到: pod 'LLNetworking', :path => '../' 這一句,path 路徑指向了本地路徑,對應(yīng) LLNetworking 主目錄下:

這個文件夾下,一個存放你的各種類文件,一個存放圖片資源等。
- podspec 文件
在你回答之前問題之后,pod 為你自動創(chuàng)建了該文件,并執(zhí)行了 pod install 命令,該命令會找到組件的索引文件(也在本地) 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
該文件為你的組件自動配置了一些基本的信息,因?yàn)槲抑笆褂眠^ trunk 登陸過,所以這里有的的賬號信息。當(dāng)然這些信息是需要你根據(jù)情況修改的,更多的配置你可以搜索相關(guān)文檔。
注意:這里的 Git 地址目前是找不到的,后期需要自己關(guān)聯(lián)。
設(shè)置共享文件
podspec 文件中 s.source_files = 'LLNetworking/Classes/**/*' 指代共享的資源路徑,我們需要將共享的文件放到這里來。

我們打開組件的目錄查看,可以看到這里已經(jīng)有了名為 ReplaceMe 的文件了,這暗示你用共享文件替換它。
podspec 文件中還有一個被注釋掉的:
# s.resource_bundles = {
# 'LLNetworking' => ['LLNetworking/Assets/*.png']
# }
這個目錄中存放一些圖片等資源,當(dāng)你需要的時候可以開啟來。
我們來創(chuàng)建一個 LLNetworking 類:
@interface LLNetworking : NSObject
-(NSString*)getSomething;
@end
@implementation LLNetworking
-(NSString *)getSomething{
return @"test method.";
}
@end
將其移動到組件的共享目錄下并刪除掉空文件ReplaceMe:

這樣,我們就設(shè)置好了共享的內(nèi)容,即組件開發(fā)好了。接下來,我們使用 Example 工程來使用這個組件的內(nèi)容。
終端進(jìn)入 Example 工程目錄下,執(zhí)行 pod install 命令來安裝組件。

我們發(fā)現(xiàn),Example 項(xiàng)目中 Pods/Development Pods/LLNetworking 下,多出來最新添加的文件。
使用組件
我們安裝好組件之后來使用一下組件的功能,就像使用三方庫那樣:
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
LLNetworking * networking = LLNetworking.new;
NSLog(@"%@",networking.getSomething);
}
控制臺輸出:
2019-11-08 17:14:47.455341+0800 LLNetworking_Example[7038:1682304] test method.
這表示功能正常。
在組件開發(fā)過程中,使用
pod 'LLNetworking', :path => '../'將路徑指向本地是很有必要的,方便測試你的組件配置是否正確,功能是否完善,相比推到遠(yuǎn)程、發(fā)布再集成,這方便太多了。
三方依賴庫
有時候,我們的組件還依賴其他的組件,又或者是三方庫。我們通過 s.dependency 字段去設(shè)置,多個庫可以分開寫多次。
在 Podfiles 模版里最后一條已經(jīng)為我們添加好了,所依賴的是 AFNetworking ,正好是我們網(wǎng)絡(luò)請求組件所依賴的,我們把它開啟,重新 pod install :
Analyzing dependencies
Fetching podspec for `LLNetworking` from `../`
Downloading dependencies
Installing AFNetworking (2.7.0)
……
我們發(fā)現(xiàn),Example 自動拉取了組件 LLNetworking 所依賴的其他組件。CocoaPods 工具的另外一個優(yōu)點(diǎn)就是,多個組件依賴同一個組件時,它會自動幫你檢測安裝,而不會重復(fù)導(dǎo)入。

我們發(fā)現(xiàn) Example 工程的 Pods中,本地開發(fā)的組件和遠(yuǎn)程發(fā)布的組件被分別放在了不同的目錄下。
有了 AFNetworking 之后,你就可以修改你的網(wǎng)絡(luò)請求組件了:
#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)建、文件共享、本地化測試使用和更新。但是,我們的組件畢竟是要服務(wù)于宿主工程的,如果僅僅只能是通過本地集成,那意義不大,我們要將其關(guān)聯(lián)到遠(yuǎn)程服務(wù)器,推送到本地搭建的 GitLab,又或者是 GitHub、碼云、Coding 等平臺。
關(guān)聯(lián)遠(yuǎn)程倉庫
在模版 podspec 文件中,已經(jīng)幫我們指定了一個 GitHub 的倉庫地址,
s.homepage = 'https://github.com/LOLITA0164/LLNetworking'
你可以使用它或者進(jìn)行修改它。我們這里選擇使用它,先去 GitHub 創(chuàng)建對應(yīng)的倉庫。

在最初創(chuàng)建組件時,系統(tǒng)已經(jīng)幫我們創(chuàng)建好了本地 Git 倉庫,進(jìn)入到項(xiàng)目中,顯示出隱藏文件夾就可以看到(command+shift+. 顯隱):

如果沒有,你可以使用命令 git init 創(chuàng)建一個?,F(xiàn)在,我們要將之前的修改進(jìn)行提交(本地提交)。
git commit -am "第一次提交"
然后我們要把本地的 Git 倉庫和剛剛創(chuàng)建的遠(yuǎn)程倉庫進(jìn)行關(guān)聯(lián)。如何關(guān)聯(lián)呢?你在網(wǎng)站上創(chuàng)建項(xiàng)目后有了這樣的提示:

這里有三種:創(chuàng)建一個新的倉庫,推送一個已存在的倉庫以及從其他倉庫導(dǎo)入。我們這里使用第二種即可。
添加遠(yuǎn)程倉庫:
git remote add origin https://github.com/LOLITA0164/LLNetworking.git
將本地內(nèi)容推送到遠(yuǎn)程倉庫:
git push -u origin master
可能會出現(xiàn)讓你登陸驗(yàn)證,輸入你的用戶名和密碼即可。出現(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 刷新一下即可看到你的提交記錄。
上述是通過終端命令進(jìn)行 git 操作,如果你并不熟悉 git 命令,你大可以使用便捷的可視化工具(上一章節(jié)有所提及),僅需簡單的點(diǎn)擊操作即可完成項(xiàng)目的管理。
打 tag 并發(fā)布到 Cocoapods
打標(biāo)簽
至此,我們已經(jīng)成功的將本地倉庫關(guān)聯(lián)并推送到遠(yuǎn)程倉庫,現(xiàn)在我們要發(fā)布一個可用的組件。
首先我們要給當(dāng)前項(xiàng)目打一個 tag 版本號,在 podspec 中:
s.version = '0.1.0'
指定的版本號是 0.1.0,那么我們就同樣打個 0.1.0 的 tag:
$ git tag 0.1.0 // 打 tag
$ git push --tags // 推送到遠(yuǎn)程
打 tag 默認(rèn)在當(dāng)前分支上,因?yàn)檫@里只有 master,所以不用切換分支,如果后期有其他分支,注意別弄錯了。
刷新頁面,項(xiàng)目的 release 選項(xiàng)中會出現(xiàn)剛剛打的版本。

你也可以直接在頁面的 release 下添加新的 tag,點(diǎn)擊 release 可以看到編輯頁面:

發(fā)布到 CocoaPods
由于我們創(chuàng)建的項(xiàng)目以及標(biāo)簽的版本號都是沿用了 podspec 文件中的信息,因此可以直接驗(yàn)證 podspec 文件信息是否可以通過驗(yàn)證,如果需要調(diào)整,調(diào)整之后最好同樣先驗(yàn)證:
pod spec lint
podspec文件的版本號一定要和tag保持一致。
如果通過驗(yàn)證,那么你會看到類似下面的提示,綠色的 passed validation:

現(xiàn)在可以將 podspec 文件提交到 CocoaPods 上了:
首先要通過 trunk 注冊生成一條會話:
// pod trunk register 郵箱 用戶名 描述
pod trunk register 476512340@qq.com LOLITA0164 --description=組件化demo
然后去郵箱進(jìn)行驗(yàn)證,驗(yàn)證成功會出現(xiàn)下面頁面:

現(xiàn)在,就可以將 podspec 提交給 CocoaPods 了。這個文件將是別人搜索你的組件的索引。
pod trunk push LLNetworking.podspec --allow-warnings
上傳完成之后,接可以通過 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 的集成?,F(xiàn)在我們需要將該組件集成到宿主工程中去,這部分沒什么好提的,因?yàn)槭褂梅绞胶图扇綆焓且粯拥?,可以說三方庫只不過是他人編寫的功能組件而已,我們的組件同樣可以提供給小組成員使用,相比于純粹的三方庫,我們的許多組件都關(guān)聯(lián)了業(yè)務(wù)部分,或者基于私人的其他組件,因此適用范圍較小。
小結(jié)
本章節(jié)先介紹了如何通過 pod 的模版工程創(chuàng)建組件,組件的配置,集成本地組件,然后介紹了遠(yuǎn)程倉庫的關(guān)聯(lián),支持 CocoaPods 的集成等內(nèi)容,學(xué)會了這些,你就可以將自己得意的功能庫提供給他人使用。在組件化的過程中,Git 是我們必須要掌握的,即使你不會使用命令,但是一定要熟悉相關(guān)的軟件。