iOS組件化方案(二)
概述
這是iOS組件化方案-總結(jié)的第二篇,在本文中我實現(xiàn)了Target-Action方案的Demo,并與***篇介紹的protocol方案做出對比。
如果沒有看過我***篇protocol組件化方案的同學(xué),可以先去下載我那篇文章中提供的Demo,方便理解我本文的詳述以及了解我Demo中實現(xiàn)的業(yè)務(wù)場景,傳送門iOS組件化方案-總結(jié)的***篇
Target-Action方案
國際慣例先上Demo(下載主工程就好了哈,如果不能理解可以把所有業(yè)務(wù)模塊都下載下來,Casa也提供了官方Demo,我***篇文章中提供了傳送門)
以下鏈接可到原文查看。
- Target-Action方案主工程
- Target-Action方案商品詳情業(yè)務(wù)Category組件地址
- Target-Action方案商品詳情業(yè)務(wù)模塊地址
- Target-Action方案確認(rèn)訂單Category組件地址
- Target-Action方案確認(rèn)訂單業(yè)務(wù)模塊地址
- Target-Action方案CTMediator地址因Casa沒有把CTMediator做成公有庫,所以我是直接拷貝過來做成我的私有庫了。
實施
如何把模塊做成私有pods我這里就不介紹了,想知道的可以看我***篇組件化介紹文章。我這里只拿確認(rèn)訂單模塊舉例
確認(rèn)訂單模塊是個單獨(dú)的project,為了避免其他模塊調(diào)用確認(rèn)訂單模塊需引入整個模塊,這里又做了一個確認(rèn)訂單業(yè)務(wù)Category的私有組件如下圖
TAConfirmOrderBusinessCategory即是確認(rèn)訂單模塊對外提供服務(wù)的入口,我們的業(yè)務(wù)場景是商品詳情模塊立即購買進(jìn)入確認(rèn)訂單模塊,確認(rèn)訂單模塊提交訂單后返回商品詳情模塊,同時得到通知下單成功,所以上圖中入?yún)⑻峁┝薈onfirmComplete的Block,下圖是TAConfirmOrderBusinessCategory.m中的實現(xiàn)
- #import "CTMediator+TAConfirmOrder.h"
- @implementation CTMediator (TAConfirmOrder)
- - (UIViewController *)confirmOrderViewControllerWithGoodsId:(NSString *)goodsId goodsName:(NSString *)goodsName ConfirmComplete:(dispatch_block_t)confirmComplete
- {
- NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
- params[@"goodsId"] = goodsId;
- params[@"goodsName"] = goodsName;
- params[@"completeBlock"] = confirmComplete;
- return [self performTarget:@"TAConfirmOrder" action:@"ConfirmOrderViewController" params:params shouldCacheTarget:NO];
- }
- @end
OK,TAConfirmOrderBusinessCategory實現(xiàn)完了,我們來看下TAConfirmOrder模塊,模塊中定義一個Target_TAConfirmOrder具體實現(xiàn)如下圖
- @interface Target_TAConfirmOrder : NSObject
- - (UIViewController *)Action_ConfirmOrderViewController:(NSDictionary *)params;
- @end
- @implementation Target_TAConfirmOrder
- - (UIViewController *)Action_ConfirmOrderViewController:(NSDictionary *)params
- {
- TAConfirmOrderViewController *confirmOrderVC = [[TAConfirmOrderViewController alloc] init];
- confirmOrderVC.goodsId = params[@"goodsId"];
- confirmOrderVC.goodsName = params[@"goodsName"];
- confirmOrderVC.confirmComplete = params[@"completeBlock"];
- return confirmOrderVC;
- }
- @end
既然TAConfirmOrderBusinessCategory和TAConfirmOrder是2個project,那category是如何調(diào)用到Target_TAConfirmOrder的呢?其實很簡單,我想看這篇文章的人大部分都知道,無非就是NSClassFromString ,performSelector這些方法,不知道的可以閱讀源碼
到這里我都沒有貼過架構(gòu)圖或者講過原理,只是貼了一部分代碼和講述如何實現(xiàn),為什么?其實組件化原理很簡單,簡單到比當(dāng)初學(xué)習(xí)UITableView容易多了,我的Demo即原理,如果還是看不明白可以自行g(shù)oogle一輪或者在評論區(qū)提問.
Target_Action VS Protocol方案
1.是否需要注冊?
- Target_Action方案不需要注冊
- Protocol方案需要在啟動的時候向CRProtocolManager注冊
Target_Action很好的利用了runtime特性,減少注冊這一步,不過對于即將切到Swift的同學(xué)就有點(diǎn)尷尬了。
在上篇提供的Protocol方案Demo中,在向CRProtocolManager注冊服務(wù)的是實例對象而非Class,這樣確實會造成內(nèi)存常駐,但是無傷大雅,熟悉runtime的同學(xué)應(yīng)該都知道***次調(diào)用某個類或?qū)ο蟮姆椒ǎ瑫?gòu)建出類對象,所以無論你用Class注冊還是實例對象注冊類對象都在,拋開類對象對于一個不掛任何property的實例對象所占用的內(nèi)存是很小的。當(dāng)然你可能會問既然都差不多你為什么注冊實例而不是注冊Class,注冊的ServiceProvider實例對象在有些情況下可以記錄一些狀態(tài),當(dāng)然這只是極少數(shù)情況下出現(xiàn)的,你如果真要把ServiceProvider當(dāng)單例對象用,我還是強(qiáng)烈建議注冊Class
不過我不認(rèn)為ServiceProvider需要向中間件注冊有邏輯上的問題,區(qū)別只是可省可不省
2.依賴關(guān)系
Target_Action方案中商品詳情模塊依賴TAConfirmOrderBusinessCategory組件來獲取確認(rèn)訂單模塊的服務(wù)
Protocol方案中商品詳情模塊需要依賴CRConfirmOrderServiceProtocol通過CRProtocolManager組件獲取提供服務(wù)的實例對象,同時確認(rèn)訂單模塊也依賴CRConfirmOrderServiceProtocol來注冊服務(wù)
乍一看Protocol方案依賴關(guān)系好像對業(yè)務(wù)產(chǎn)生了侵入因為調(diào)用方和實現(xiàn)方都同時依賴了CRConfirmOrderServiceProtocol,其實CRConfirmOrderServiceProtocol應(yīng)當(dāng)屬于確認(rèn)訂單模塊的一部分,把他獨(dú)立出來只是為了避免調(diào)用方需要直接引用實現(xiàn)方,這個依賴在架構(gòu)圖中體現(xiàn)的應(yīng)該是虛線而不是實線。試想如果Target_Action方案不用runtime,那BusinessCategory也需要直接依賴Target。利用runtime中NSProtocolFromString也可以解決對CRConfirmOrderServiceProtocol的依賴,只是造成一定量的硬編碼不夠優(yōu)雅。(提一下,雖然runtime在一些特定場景給我們開發(fā)帶來一些意想不到的奇效,但是runtime跳過了編譯器檢查,有時候排除bug比較艱難,所以還是慎用)
另Protocol方案中商品詳情模塊同時依賴CRConfirmOrderServiceProtocol和CRProtocolManager而Target_Action方案中商品詳情模塊僅依賴TAConfirmOrderBusinessCategory,依賴關(guān)系如下圖
Protocol方案橫向依賴了2者,Target_Action方案縱向依賴。Target_Action設(shè)計的更優(yōu)異
3.可讀性、硬編碼
Target_Action在Category中將常規(guī)參數(shù)打包成字典,在Target處再把字典拆包成常規(guī)參數(shù),這造成了一定量的硬編碼,不過在現(xiàn)實開發(fā)中,一個模塊一個模塊提供的category通常是一個人寫的,所以造成的影響微乎其微,但是給其他閱讀代碼的人帶來一些不便,甚至同一個人寫Cagetory、Target的時候也需要在2個project不停切換查看之前在Target中定義的函數(shù)名
Protocol方案中0硬編碼,可讀性更高。
在這里提一下Url注冊方案,Url注冊方案我覺得***的問題是大量的硬編碼,可讀性很差,維護(hù)性也很差,對文檔的依賴度很高,而且需要有人不停督促文檔的更新。我想很多同學(xué)對此都深有體會,每個項目***版的接口文檔相對比較詳細(xì)、全面,隨著版本迭代更新,某個接口增加了一些字段,通常后臺開發(fā)人員都是忘記去更新文檔也許是因為忙,甚至有些同學(xué)懶的更新文檔,一般這時候都是通過qq或者其他通訊工具告知客戶端開發(fā)人員增加了哪些個字段,字段含義是什么。待時間長了,客戶端開發(fā)人員忘記字段含義或者換了另一個開發(fā)人員接手,不知道這個字段含義是什么,先去翻看以前聊天記錄,找不到去看接口文檔,文檔還是1.0版本。。。。我去。。。
總結(jié)
綜合以上3點(diǎn),Target_Action更優(yōu),我們公司目前也采用的Target_Action方案,如果有同學(xué)有計劃切到Swift語言開發(fā),我建議Protocol方案。事實上沒有哪個方案是***的,具體的采用還得結(jié)合自己的業(yè)務(wù)以及開發(fā)人員的整體素質(zhì),如果你還是拿不定主意,阿里開源了一個模塊解耦框架BeeHive(protocol注冊),你就向著大廠靠攏吧。Url注冊方案Demo我就不提供了,因為它的可讀性,維護(hù)性以及常規(guī)參數(shù)傳遞的缺點(diǎn)讓我放棄了它,不過Url注冊方案配合服務(wù)器下發(fā)路由能夠很好的解決bug,前提是你們的模塊得Native開發(fā)一套 H5開發(fā)一套(或者RN和Weex)
補(bǔ) 業(yè)務(wù)模塊的劃分
有不少同學(xué)知道了組件化,但是不知道如何去劃分業(yè)務(wù)模塊,我大致拿京東App某幾個業(yè)務(wù)舉例見下圖
圖片每個Module組件化后就是一個單獨(dú)的project,也許很多project里面只有一個ViewController,這也是合理的劃分,比如商品詳情,很多模塊(服裝城,京東超市,全球購。。。)會調(diào)用到商品詳情模塊,那把商品詳情模塊中的業(yè)務(wù)強(qiáng)行塞到(服裝城,京東超市,全球購。。。)任何一個project都是不合理的,確認(rèn)訂單同理。組件化是把業(yè)務(wù)縱切,具體到某個業(yè)務(wù)模塊中network模塊,database模塊的劃分是橫切
預(yù)告
發(fā)現(xiàn)很多同學(xué)理解MVC的姿勢不對,導(dǎo)致controller很臃腫難以維護(hù),下一篇我會把自己理解的MVC寫成一個Demo,這個Demo會是一個業(yè)務(wù)比較龐大的模塊(所以時間會長一點(diǎn)。畢竟我白天要忙公司的項目,還有幾個個人項目需要維護(hù))在這個Demo中職責(zé)劃分會很清楚,敬請期待哈。