組件、模塊的區(qū)別和聯(lián)系
組件:提供特定單一功能或則通用可復(fù)用UI;把一些可復(fù)用的UI元素或則單一功能,抽離為單個組件,便于項目的維護和開發(fā)。多個組件可以組合成組件庫,方便調(diào)用和復(fù)用,組件間也可以嵌套,小組件組合成大組件
模塊(模塊組件):分屬同一業(yè)務(wù)的代碼進行隔離(分裝)成獨立的模塊,可以獨立運行,以頁面、功能或其他不同粒度劃分程度不同的模塊,位于業(yè)務(wù)框架層,模塊間通過接口調(diào)用,目的是降低模塊間的耦合;把一些可復(fù)用的代碼,抽離為單個模塊,便于項目的維護和開發(fā)??梢哉{(diào)用組件來組成模塊,多個模塊可以組合成業(yè)務(wù)框架。
業(yè)務(wù)線模塊(業(yè)務(wù)框架層):該業(yè)務(wù)線下關(guān)聯(lián)的多個模塊組件的框架層(虛擬的層)
為什么使用組件化和模塊化?
開發(fā)和調(diào)試效率高:隨著功能越來越多,代碼結(jié)構(gòu)會越發(fā)復(fù)雜,要修改某一個小功能,可能要重新翻閱整個項目的代碼,把所有相同的地方都修改一遍,重復(fù)勞動浪費時間和人力,效率低;使用組件化,每個相同的功能結(jié)構(gòu)都調(diào)用同一個組件,只需要修改這個組件,即可全局修改。
可維護性強:便于后期代碼查找和維護。
避免阻斷:模塊化是可以獨立運行的,如果一個模塊產(chǎn)生了bug,不會影響其他模塊的調(diào)用。
版本管理更容易:如果由多人協(xié)作開發(fā),可以避免代碼覆蓋和沖突。
組件-模塊關(guān)系示意圖:

制作私有pod組件的過程
組件創(chuàng)建步驟
官方示例:https://code.tutsplus.com/tutorials/creating-your-first-cocoapod--cms-24332
參考文獻:http://www.itdecent.cn/p/7b4667cde80b
1、開個遠程代碼托管庫,庫地址未url
2、git clone url 到本地文件目錄下path
3、在path目錄下,執(zhí)行pod lib create xxxx,創(chuàng)建pod模版工程
4、在pod模版目錄文件中,找到xxx.podspec文件所在目錄,修改配置下xxx.podspec文件(也可以不配置,等最后版本提交時再修改配置); 在和xxx.podspec文件同一層級目錄下,有個xxx目錄,這個目錄中包含/Classes(存放.h、.m文件)和/Assets(存放圖片、xib、音視頻等資源,這些資源會打包在xxx.framework中;和xxx.podspec文件同目錄下,有個/Example目錄,打開xxx.xcworkspace工程,在Pods工程下xxx文件目錄下,創(chuàng)建工程代碼,每有了代碼改動尤其是類的增加/減少、資源文件增減都要對/Example目錄執(zhí)行下pod install,不然,xxx.xcworkspace中可能引用不到對應(yīng)新增/減少的類、資源,podspec文件官方詳解
5、每次開發(fā)完,及時將代碼提交到遠程托管庫;當這個版本的功能開發(fā)完后,修改校驗xxx.podspec文件(發(fā)布版本時,必改項是版本號version),給版本號打tag,必須打tag,因為xxx.podspec中的s.version就是這個tag
?$ git tag -a 0.0.1 -m “V0.0.1”
?$ git push origin version號
?$ git push —tags
6、驗證.podspec是否配置正確
?$ cd podspec文件所在目錄
?$ pod lib lint (驗證)
?$ pod lib lint —verbose (驗證-并顯示詳細信息)
?$ pod lib lint —allow-warnings? (驗證—忽略警告)
?$ pod spec lint podName.podspec —verbose? (這個從本地和遠程驗證你的pod能否通過驗證,上面三個都是從本地驗證你的pod能否通過驗證)
?7、至此一個pod組件制作完畢,但是當有多個pod組件時,為便于管理這些組件庫,就創(chuàng)建了Spec Repo,來統(tǒng)一管理多個pod組件的代碼庫地址url;
?// 創(chuàng)建Spec Repo遠程私有托管庫
?$ pod repo add REPO_NAME SOURCE_URL
?// 在本地cocoapods目錄下查找REPO_NAME是否創(chuàng)建成功
?$ cd ~/.cocoapods/repos/REPO_NAME
?8、把xxx.podspec添加到Spec Repo中,這樣多個pod組件庫的下載就可以直接訪問Spec Repo來下載對應(yīng)的組件庫了
?$ pod repo push REPO_NAME SPEC_NAME.podspec
?9、后續(xù)每次迭代開發(fā)提交,重復(fù)5、6、8的操作(后續(xù)迭代開發(fā)操作6可以省略)
?10、隨著版本的迭代,某一個功能可能會拆分成幾個模塊,這時就會用到subspec,將一個大組件拆分成幾個小的子組件subspec,官網(wǎng)文獻鏈接


資源文件的加載
不管是.a庫,還是framework庫,使用圖片資源,都可以將資源放進bundle中存放,.a/framework庫文件加載資源從bundle中取圖片;
制作三方庫在考慮Static Library格式的庫庫或者Framework格式的庫時:依賴圖片資源,使用Framework格式的庫;想要擁有完整的依賴關(guān)系,使用Framework格式的庫,此時外界可能需要剔除Framework格式的庫之外的依賴庫,而采用Framework格式的庫內(nèi)的依賴庫,否則雖然不會產(chǎn)生依賴沖突,但會增加包大小
制作三方庫或者使用三方庫過程中,比如A組件和B組件命名了相同名字的類文件,會出現(xiàn)命名沖突報duplicate symbol錯誤的問題,對于制作framework或則.a組件時,命名規(guī)范,以庫的前綴為類名,避免重名問題;
符號沖突文獻:
http://www.itdecent.cn/p/9cf63c30f650
http://www.itdecent.cn/p/2591c0ec07f2
幾種三方庫制作過程mach -O Type選型區(qū)別:(build settings -> linking ->mach -O Type)
Executable: `靜態(tài)庫`,輸出二進制
Dynamic Library:`動態(tài)非共享庫`,輸出動態(tài)鏈接庫非共享庫,程序`運行`時鏈接到`內(nèi)存`,大部分場景下不可共享;app extension、部分macOS場景下可以共享
Bundle:`動態(tài)非共享庫`,和Dynamic Library相近,不過需要手動調(diào)用函數(shù)加載
Static Library:? `靜態(tài)庫`,輸出靜態(tài)鏈接庫,程序`編譯`時拷貝到`內(nèi)存`
Relocatable Object File:`靜態(tài)庫`,和Static Library類似,但體積更小
cocopods官網(wǎng)說明資源加載有兩種方式:
一、spec.resources、spec.resource,這種方式,資源文件會直接放置在組件所在的framework中(如下面的ResourceStyleSpec.framework),
資源路徑查找方式:
1)獲取當前組件的framework(也是一種bundle)的方式為:
NSBundle *bundle = [NSBundle bundleForClass:[self class]]
2)查找具體資源路徑:
NSString *xxxPath= [bundle pathForResource:@"xxx" ofType:@"xib/png/..."];
二、spec.resource_bundles、?spec.resource_bundle,這種方式,資源文件會放置在組件所在的framework下的專門的資源bundle中(如下面的ResourceStyleSpec.framework下的ResourceStyleSpec.bundle)
資源路徑查找方式:
1)獲取當前組件XXX的framework(也是一種bundle)的方式為:
NSBundle *bundle = [NSBundle bundleForClass:[self?class]]
2)獲取當前組件的framework下的資源bundle:XXX.bundle:
NSBundle *sourceBundle = [NSBundle bundleWithPath:[bundle ?pathForResource:@"XXX" ofType:@"bundle"]];?
3)查找具體資源路徑:
NSString *xxxPath= [sourceBundle pathForResource:@"xxx" ofType:@"xib/png/..."];
相關(guān)資料:
http://www.itdecent.cn/p/871e1b8891d8


組件的通信
一、組件的業(yè)務(wù)粒度劃分
關(guān)于組件業(yè)務(wù)粒度劃分,沒有統(tǒng)一的標準,根據(jù)實際業(yè)務(wù)來界定組件提供功能服務(wù)的邊界;比如支付組件,就是提供一個完整的支付流程的組件,外界只需要集成該組件,通過調(diào)用簡單的組件提供的API就可以完成支付業(yè)務(wù)。
二、組件依賴方式確定
未組件化時,各業(yè)務(wù)模塊耦合在一起的模塊依賴關(guān)系:



通過上面三種方案的對比,可以看到:
業(yè)務(wù)組件和中間調(diào)度模塊相互依賴時,中間調(diào)度模塊依賴業(yè)務(wù)組件,當依賴了中間調(diào)度模塊,也間接依賴了業(yè)務(wù)組件,并沒有達到解耦的目的,是不合理的;因此,為了達到真正的解耦,應(yīng)該是業(yè)務(wù)組件依賴中間調(diào)度模塊,業(yè)務(wù)組件需要對外開發(fā)的實體,注冊到中間調(diào)度模塊中,外部通過調(diào)用中間調(diào)度模塊來獲取業(yè)務(wù)組件實體,外部并不依賴業(yè)務(wù)組件,從而達到解耦的目的,即依賴關(guān)系采用第三種-業(yè)務(wù)組件單向依賴中間調(diào)度模塊的方案
三、組件間通訊技術(shù)方案實現(xiàn)
目前比較流行的組件間通訊技術(shù)方案有三種:scheme方式、protocol方式、target_action方式。三種方式都能實現(xiàn)組件間通訊,其中scheme方式和protocol方式都必須在運行內(nèi)存中維護一個注冊表,target_action方式不必維護這個注冊表(當然使用者按需求想維護一個也是可以支持的),因此,從解耦的徹底性來看,target_action方式解耦更為徹底,但是組件間的通訊作為一個支撐所有場景下的組件間通訊方案,解耦只是其中一個的量化指標,組件通訊代碼和組件調(diào)用鏈路代碼可維護性和易接入性等也是量化指標,個人使用這三種方式的過程中,總結(jié)了這三種方式的選型場景,選擇組件化方案時,可以適度參考一下幾點:
1、純iOS端組件間調(diào)用,使用protocol,可以傳圖片UIImage對象等非基礎(chǔ)數(shù)據(jù)類型參數(shù);但不能跨端使用
2、scheme方式場景一:系統(tǒng)通用跨端能力實現(xiàn),比如短信鏈接、端外push、h5、瀏覽器、其他APP等跨端跳轉(zhuǎn)到APP的某一個頁面,就必須使用scheme方式,采用openUrl方式打開;
scheme方式場景二:APP內(nèi)部組件間調(diào)用和組件內(nèi)部調(diào)用使用scheme方式也是scheme的領(lǐng)一種使用方式
3、target_action方式,解耦比較徹底,完全利用系統(tǒng)的消息發(fā)送機制,不需要維護一個注冊表,但也導(dǎo)致了一些特殊場景,比如需要拿到先前創(chuàng)建過的實例進行跳轉(zhuǎn),雖然CTMediator組件中提供了cachedTarget來緩存實例,但是這個實例是第一次調(diào)用CTMediator后才存儲的,實際場景這個實例是外界創(chuàng)建的,這樣使用CTMediator就拿不到這個外界創(chuàng)建的實例了。如果?CTMediator也維護一個外界注冊的實例表,和protocol和scheme的方式差異也不大了
作為一個APP,尤其一個大型的項目APP,除了APP內(nèi)部通訊,跨端通訊能力也是必須的,因此,不管是采用protocol還是使用target_action方式,由于牽涉到跨端通訊,因為都會使用系統(tǒng)通用跨端通信能力,即scheme的方式場景一。因為項目中,組件通訊方案都是protocol或者target_action方式結(jié)合scheme方式使用的,即在需要用到scheme的解析的case,根據(jù)約定的scheme字段,使用對應(yīng)約定的protocol或則arget_actio去做組件間的通訊如跳轉(zhuǎn);用不到scheme的就按照protocol或者target_action方式進行組件間通訊。