轉(zhuǎn)載自Casa Taloyum架構(gòu)系列文章:
iOS應(yīng)用架構(gòu)談 開(kāi)篇
iOS應(yīng)用架構(gòu)談 view層的組織和調(diào)用方案
iOS應(yīng)用架構(gòu)談 網(wǎng)絡(luò)層設(shè)計(jì)方案
iOS應(yīng)用架構(gòu)談 本地持久化方案及動(dòng)態(tài)部署
iOS應(yīng)用架構(gòu)談 組件化方案
在現(xiàn)有工程中實(shí)施基于CTMediator的組件化方案
前述
國(guó)內(nèi)業(yè)界大家對(duì)組件化的討論從今年年初開(kāi)始到年尾,不外乎兩個(gè)方案:URL/protocol注冊(cè)調(diào)度凄硼,runtime調(diào)度夏块。
我之前批評(píng)過(guò)URL注冊(cè)調(diào)度是錯(cuò)誤的組件化實(shí)施方案,在所有的基于URL注冊(cè)調(diào)度的方案中萧福,存在兩個(gè)普遍問(wèn)題:
- 命名域滲透
- 因注冊(cè)是不必要的耻矮,而帶來(lái)同樣不必要的注冊(cè)列表維護(hù)成本
其它各家的基于URL注冊(cè)的不同方案在這兩個(gè)普遍問(wèn)題上還有各種各樣的其他問(wèn)題忧吟,例如FRDIntent庫(kù)中的FRDIntent對(duì)象其本質(zhì)是雞肋對(duì)象被啼、原屬于響應(yīng)者的業(yè)務(wù)被滲透到調(diào)用者的業(yè)務(wù)中试躏、組件化實(shí)施方案的過(guò)程中會(huì)產(chǎn)生對(duì)原有代碼的侵入式修改等問(wèn)題猪勇。
另外,我也發(fā)現(xiàn)還是有人在都沒(méi)有理解清楚的前提下就做出了自己的解讀颠蕴,流毒甚廣泣刹。我之前寫(xiě)過(guò)關(guān)于CTMediator比較理論的描述,也有Demo裁替,但惟獨(dú)沒(méi)有寫(xiě)實(shí)踐方面的描述项玛。我本來(lái)以為Demo就足夠了,可現(xiàn)在看來(lái)還是要給一篇實(shí)踐的文章的弱判。
在更早之前襟沮,卓同學(xué)的swift老司機(jī)群里也有人提出因?yàn)樽约翰](méi)有理解透徹CTMediator方案,所以不敢貿(mào)然直接在項(xiàng)目中應(yīng)用昌腰。所以這篇文章的另一個(gè)目的也是希望能夠讓大家明白开伏,基于CTMediator的組件化方案實(shí)施其實(shí)非常簡(jiǎn)單,而且也是有章法可循的遭商。這篇文章可能會(huì)去討論一些理論的東西固灵,但主要還會(huì)是以實(shí)踐為主。爭(zhēng)取做到能夠讓大家看完文章之后就可以直接在自己的項(xiàng)目中順利實(shí)施組件化劫流。
最后巫玻,我希望這篇文章能夠終結(jié)業(yè)界持續(xù)近一年的關(guān)于組件化方案的無(wú)謂討論和錯(cuò)誤討論。
準(zhǔn)備工作
我在github上開(kāi)了一個(gè)orgnization祠汇,里面有一個(gè)主工程:MainProject仍秤,我們要針對(duì)這個(gè)工程來(lái)做組件化。組件化實(shí)施完畢之后的主工程就是ModulizedMainProject了可很。抽出來(lái)的獨(dú)立Pod诗力、私有Pod源也都會(huì)放在這個(gè)orgnization中去。
在一個(gè)項(xiàng)目實(shí)施組件化方案之前我抠,我們需要做一個(gè)準(zhǔn)備工作苇本,建立自己的私有Pod源和快手工具腳本的配置:
- 先去開(kāi)一個(gè)repo袜茧,這個(gè)repo就是我們私有Pod源倉(cāng)庫(kù)
pod repo add [私有Pod源倉(cāng)庫(kù)名字] [私有Pod源的repo地址]
- 創(chuàng)立一個(gè)文件夾,例如Project瓣窄。把我們的主工程文件夾放到Project下:
~/Project/MainProject
- 在~/Project下clone快速配置私有源的腳本repo:
git clone git@github.com:casatwy/ConfigPrivatePod.git
- 將ConfigPrivatePod的template文件夾下Podfile中
source 'https://github.com/ModulizationDemo/PrivatePods.git'
改成第一步里面你自己的私有Pod源倉(cāng)庫(kù)的repo地址 - 將ConfigPrivatePod的template文件夾下upload.sh中
PrivatePods
改成第二步里面你自己的私有Pod源倉(cāng)庫(kù)的名字
最后你的文件目錄結(jié)構(gòu)應(yīng)該是這樣:
Project
├── ConfigPrivatePod
└── MainProject
到此為止笛厦,準(zhǔn)備工作就做好了。
實(shí)施組件化方案第一步:創(chuàng)建私有Pod工程和Category工程
MainProject是一個(gè)非常簡(jiǎn)單的應(yīng)用康栈,一共就三個(gè)頁(yè)面递递。首頁(yè)push了AViewController,AViewController里又push了BViewController啥么。我們可以理解成這個(gè)工程由三個(gè)業(yè)務(wù)組成:首頁(yè)登舞、A業(yè)務(wù)、B業(yè)務(wù)悬荣。
我們這一次組件化的實(shí)施目標(biāo)就是把A業(yè)務(wù)組件化出來(lái)菠秒,首頁(yè)和B業(yè)務(wù)都還放在主工程。
因?yàn)樵趯?shí)際情況中氯迂,組件化是需要循序漸進(jìn)地實(shí)施的践叠。尤其是一些已經(jīng)比較成熟的項(xiàng)目,業(yè)務(wù)會(huì)非常多嚼蚀,一時(shí)半會(huì)兒是不可能完全組件化的禁灼。CTMediator方案在實(shí)施過(guò)程中,對(duì)主工程業(yè)務(wù)的影響程度極小轿曙,而且是能夠支持循序漸進(jìn)地改造方式的弄捕。這個(gè)我會(huì)在文章結(jié)尾做總結(jié)的時(shí)候提到。
既然要把A業(yè)務(wù)抽出來(lái)作為組件导帝,那么我們需要為此做兩個(gè)私有Pod:A業(yè)務(wù)Pod(以后簡(jiǎn)稱A Pod)守谓、方便其他人調(diào)用A業(yè)務(wù)的CTMediator category的Pod(以后簡(jiǎn)稱A_Category Pod)。這里多解釋一句:A_Category Pod本質(zhì)上只是一個(gè)方便方法您单,它對(duì)A Pod不存在任何依賴斋荞。
我們先創(chuàng)建A Pod
- 新建Xcode工程,命名為A虐秦,放到Projects下
- 新建Repo平酿,命名也為A,新建好了之后網(wǎng)頁(yè)不要關(guān)掉
此時(shí)你的文件目錄結(jié)構(gòu)應(yīng)該是這樣:
Project
├── ConfigPrivatePod
├── MainProject
└── A
然后cd到ConfigPrivatePod下悦陋,執(zhí)行./config.sh
腳本來(lái)配置A這個(gè)私有Pod染服。腳本會(huì)問(wèn)你要一些信息,Project Name
就是A叨恨,要跟你的A工程的目錄名一致。HTTPS Repo
挖垛、SSH Repo
網(wǎng)頁(yè)上都有痒钝,Home Page URL就填你A Repo網(wǎng)頁(yè)的URL就好了秉颗。
這個(gè)腳本是我寫(xiě)來(lái)方便配置私有庫(kù)的腳本,pod lib create
也可以用送矩,但是它會(huì)直接從github上拉一個(gè)完整的模版工程下來(lái)蚕甥,只是國(guó)內(nèi)訪問(wèn)github其實(shí)會(huì)比較慢,會(huì)影響效率栋荸。而且這個(gè)配置工作其實(shí)也不復(fù)雜菇怀,我就索性自己寫(xiě)了個(gè)腳本。
這個(gè)腳本要求私有Pod的文件目錄要跟腳本所在目錄平級(jí)晌块,也會(huì)在XCode工程的代碼目錄下新建一個(gè)跟項(xiàng)目同名的目錄爱沟。放在這個(gè)目錄下的代碼就會(huì)隨著Pod的發(fā)版而發(fā)出去,這個(gè)目錄以外的代碼就不會(huì)跟隨Pod的版本發(fā)布而發(fā)布匆背,這樣子寫(xiě)用于測(cè)試的代碼就比較方便呼伸。
然后我們?cè)谥鞴こ讨校褜儆贏業(yè)務(wù)的代碼拎出來(lái)钝尸,放到新建好的A工程的A文件夾里去括享,然后拖放到A工程中。原來(lái)主工程里面A業(yè)務(wù)的代碼直接刪掉珍促,此時(shí)主工程和A工程編譯不過(guò)都是正常的铃辖,我們會(huì)在第二步中解決主工程的編譯問(wèn)題,第三步中解決A工程的編譯問(wèn)題猪叙。
此時(shí)你的主工程應(yīng)該就沒(méi)有A業(yè)務(wù)的代碼了娇斩,然后你的A工程應(yīng)該是這樣:
A
├── A
| ├── A
| │ ├── AViewController.h
| │ └── AViewController.m
| ├── AppDelegate.h
| ├── AppDelegate.m
| ├── ViewController.h
| ├── ViewController.m
| └── main.m
└── A.xcodeproj
我們?cè)賱?chuàng)建A_Category Pod
同樣的,我們?cè)賱?chuàng)建A_Category沐悦,因?yàn)樗彩莻€(gè)私有Pod成洗,所以也照樣子跑一下config.sh
腳本去配置一下就好了。最后你的目錄結(jié)構(gòu)應(yīng)該是這樣的:
Project
├── A
│ ├── A
│ │ ├── A
│ │ ├── AppDelegate.h
│ │ ├── AppDelegate.m
│ │ ├── Assets.xcassets
│ │ ├── Info.plist
│ │ ├── ViewController.h
│ │ ├── ViewController.m
│ │ └── main.m
│ ├── A.podspec
│ ├── A.xcodeproj
│ ├── FILE_LICENSE
│ ├── Podfile
│ ├── readme.md
│ └── upload.sh
├── A_Category
│ ├── A_Category
│ │ ├── A_Category
│ │ ├── AppDelegate.h
│ │ ├── AppDelegate.m
│ │ ├── Info.plist
│ │ ├── ViewController.h
│ │ ├── ViewController.m
│ │ └── main.m
│ ├── A_Category.podspec
│ ├── A_Category.xcodeproj
│ ├── FILE_LICENSE
│ ├── Podfile
│ ├── readme.md
│ └── upload.sh
├── ConfigPrivatePod
│ ├── config.sh
│ └── templates
└── MainProject
├── FILE_LICENSE
├── MainProject
├── MainProject.xcodeproj
├── MainProject.xcworkspace
├── Podfile
├── Podfile.lock
├── Pods
└── readme.md
然后去A_Category下藏否,在Podfile中添加一行pod "CTMediator"
瓶殃,在podspec文件的后面添加s.dependency "CTMediator"
,然后執(zhí)行pod install --verbose
副签。
接下來(lái)打開(kāi)A_Category.xcworkspace
遥椿,把腳本生成的名為A_Category
的空目錄拖放到Xcode對(duì)應(yīng)的位置下,然后在這里新建基于CTMediator的Category:CTMediator+A
淆储。最后你的A_Category工程應(yīng)該是這樣的:
A_Category
├── A_Category
| ├── A_Category
| │ ├── CTMediator+A.h
| │ └── CTMediator+A.m
| ├── AppDelegate.h
| ├── AppDelegate.m
| ├── ViewController.h
| └── ViewController.m
└── A_Category.xcodeproj
到這里為止冠场,A工程和A_Category工程就準(zhǔn)備好了。
實(shí)施組件化方案第二步:在主工程中引入A_Category工程本砰,并讓主工程編譯通過(guò)
去主工程的Podfile下添加pod "A_Category", :path => "../A_Category"
來(lái)本地引用A_Category碴裙。
然后編譯一下,說(shuō)找不到AViewController
的頭文件。此時(shí)我們把頭文件引用改成#import <A_Category/CTMediator+A.h>
舔株。
然后繼續(xù)編譯莺琳,說(shuō)找不到AViewController
這個(gè)類型≡卮龋看一下這里是使用了AViewController
的地方惭等,于是我們?cè)?code>Development Pods下找到CTMediator+A.h
,在里面添加一個(gè)方法:
- (UIViewController *)A_aViewController;
再去CTMediator+A.m
中办铡,補(bǔ)上這個(gè)方法的實(shí)現(xiàn)辞做,把主工程中調(diào)用的語(yǔ)句作為注釋放進(jìn)去,將來(lái)寫(xiě)Target-Action要用:
- (UIViewController *)A_aViewController
{
/*
AViewController *viewController = [[AViewController alloc] init];
*/
return [self performTarget:@"A" action:@"viewController" params:nil shouldCacheTarget:NO];
}
補(bǔ)充說(shuō)明一下寡具,performTarget:@"A"
中給到的@"A"
其實(shí)是Target對(duì)象的名字秤茅。一般來(lái)說(shuō),一個(gè)業(yè)務(wù)Pod只需要有一個(gè)Target就夠了晒杈,但一個(gè)Target下可以有很多個(gè)Action嫂伞。Action的名字也是可以隨意命名的,只要到時(shí)候Target對(duì)象中能夠給到對(duì)應(yīng)的Action就可以了拯钻。
關(guān)于Target-Action我們會(huì)在第三步中去實(shí)現(xiàn)帖努,現(xiàn)在不實(shí)現(xiàn)Target-Action是不影響主工程編譯的。
category里面這么寫(xiě)就已經(jīng)結(jié)束了粪般,后面的實(shí)施過(guò)程中就不會(huì)再改動(dòng)到它了拼余。
然后我們把主工程調(diào)用AViewController
的地方改為基于CTMediator Category的實(shí)現(xiàn):
UIViewController *viewController = [[CTMediator sharedInstance] A_aViewController];
[self.navigationController pushViewController:viewController animated:YES];
再編譯一下,編譯通過(guò)亩歹。
到此為止主工程就改完了匙监,現(xiàn)在跑主工程點(diǎn)擊這個(gè)按鈕跳不到A頁(yè)面是正常的,因?yàn)槲覀冞€沒(méi)有在A工程中實(shí)現(xiàn)Target-Action小作。
而且此時(shí)主工程中關(guān)于A業(yè)務(wù)的改動(dòng)就全部結(jié)束了亭姥,后面的組件化實(shí)施過(guò)程中,就不會(huì)再有針對(duì)A業(yè)務(wù)線對(duì)主工程的改動(dòng)了顾稀。
實(shí)施組件化方案第三步:添加Target-Action达罗,并讓A工程編譯通過(guò)
此時(shí)我們關(guān)掉所有XCode窗口。然后打開(kāi)兩個(gè)工程:A_Category工程和A工程静秆。
我們?cè)贏工程中創(chuàng)建一個(gè)文件夾:Targets
粮揉,然后看到A_Category里面有performTarget:@"A"
,所以我們新建一個(gè)對(duì)象抚笔,叫做Target_A
扶认。
然后又看到對(duì)應(yīng)的Action是viewController
,于是在Target_A中新建一個(gè)方法:Action_viewController
殊橙。這個(gè)Target對(duì)象是這樣的:
頭文件:
#import <UIKit/UIKit.h>
@interface Target_A : NSObject
- (UIViewController *)Action_viewController:(NSDictionary *)params;
@end
實(shí)現(xiàn)文件:
#import "Target_A.h"
#import "AViewController.h"
@implementation Target_A
- (UIViewController *)Action_viewController:(NSDictionary *)params
{
AViewController *viewController = [[AViewController alloc] init];
return viewController;
}
@end
這里寫(xiě)實(shí)現(xiàn)文件的時(shí)候辐宾,對(duì)照著之前在A_Category里面的注釋去寫(xiě)就可以了狱从。
因?yàn)門(mén)arget對(duì)象處于A的命名域中,所以Target對(duì)象中可以隨意import A業(yè)務(wù)線中的任何頭文件叠纹。
另外補(bǔ)充一點(diǎn)矫夯,Target對(duì)象的Action設(shè)計(jì)出來(lái)也不是僅僅用于返回ViewController實(shí)例的,它可以用來(lái)執(zhí)行各種屬于業(yè)務(wù)線本身的任務(wù)吊洼。例如上傳文件,轉(zhuǎn)碼等等各種任務(wù)其實(shí)都可以作為一個(gè)Action來(lái)給外部調(diào)用制肮,Action完成這些任務(wù)的時(shí)候冒窍,業(yè)務(wù)邏輯是可以寫(xiě)在Action方法里面的。
換個(gè)角度說(shuō)就是:Action具備調(diào)度業(yè)務(wù)線提供的任何對(duì)象和方法來(lái)完成自己的任務(wù)的能力豺鼻。它的本質(zhì)就是對(duì)外業(yè)務(wù)的一層服務(wù)化封裝综液。
現(xiàn)在我們這個(gè)Action要完成的任務(wù)只是實(shí)例化一個(gè)ViewController并返回出去而已,根據(jù)上面的描述儒飒,Action可以完成的任務(wù)其實(shí)可以更加復(fù)雜谬莹。
然后我們?cè)倮^續(xù)編譯A工程,發(fā)現(xiàn)找不到BViewController
桩了。由于我們這次組件化實(shí)施的目的僅僅是將A業(yè)務(wù)線抽出來(lái)附帽,BViewController
是屬于B業(yè)務(wù)線的,所以我們沒(méi)必要把B業(yè)務(wù)也從主工程里面抽出來(lái)井誉。但為了能夠讓A工程編譯通過(guò)蕉扮,我們需要提供一個(gè)B_Category來(lái)使得A工程可以調(diào)度到B,同時(shí)也能夠編譯通過(guò)颗圣。
B_Category的創(chuàng)建步驟跟A_Category是一樣的喳钟,不外乎就是這幾步:新建Xcode工程、網(wǎng)頁(yè)新建Repo在岂、跑腳本配置Repo奔则、添加Category代碼。
B_Category添加好后蔽午,我們同樣在A工程的Podfile中本地指過(guò)去易茬,然后跟在主工程的時(shí)候一樣。
所以B_Category是這樣的:
頭文件:
#import <CTMediator/CTMediator.h>
#import <UIKit/UIKit.h>
@interface CTMediator (B)
- (UIViewController *)B_viewControllerWithContentText:(NSString *)contentText;
@end
實(shí)現(xiàn)文件:
#import "CTMediator+B.h"
@implementation CTMediator (B)
- (UIViewController *)B_viewControllerWithContentText:(NSString *)contentText
{
/*
BViewController *viewController = [[BViewController alloc] initWithContentText:@"hello, world!"];
*/
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
params[@"contentText"] = contentText;
return [self performTarget:@"B" action:@"viewController" params:params shouldCacheTarget:NO];
}
@end
然后我們對(duì)應(yīng)地在A工程中修改頭文件引用為#import <B_Category/CTMediator+B.h>
祠丝,并且把調(diào)用的代碼改為:
UIViewController *viewController = [[CTMediator sharedInstance] B_viewControllerWithContentText:@"hello, world!"];
[self.navigationController pushViewController:viewController animated:YES];
此時(shí)再編譯一下疾呻,編譯通過(guò)了。注意哦写半,這里A業(yè)務(wù)線跟B業(yè)務(wù)線就已經(jīng)完全解耦了岸蜗,跟主工程就也已經(jīng)完全解耦了。
實(shí)施組件化方案最后一步:收尾工作叠蝇、組件發(fā)版
此時(shí)還有一個(gè)收尾工作是我們給B業(yè)務(wù)線創(chuàng)建了Category璃岳,但沒(méi)有創(chuàng)建Target-Action年缎。所以我們要去主工程創(chuàng)建一個(gè)B業(yè)務(wù)線的Target-Action。創(chuàng)建的時(shí)候其實(shí)完全不需要?jiǎng)拥紹業(yè)務(wù)線的代碼铃慷,只需要新增Target_B對(duì)象即可:
Target_B頭文件:
#import <UIKit/UIKit.h>
@interface Target_B : NSObject
- (UIViewController *)Action_viewController:(NSDictionary *)params;
@end
Target_B實(shí)現(xiàn)文件:
#import "Target_B.h"
#import "BViewController.h"
@implementation Target_B
- (UIViewController *)Action_viewController:(NSDictionary *)params
{
NSString *contentText = params[@"contentText"];
BViewController *viewController = [[BViewController alloc] initWithContentText:contentText];
return viewController;
}
@end
這個(gè)Target對(duì)象在主工程內(nèi)不存在任何侵入性单芜,將來(lái)如果B要獨(dú)立成一個(gè)組件的話,把這個(gè)Target對(duì)象帶上就可以了犁柜。
收尾工作就到此結(jié)束洲鸠,我們創(chuàng)建了三個(gè)私有Pod:A、A_Category馋缅、B_Category扒腕。
接下來(lái)我們要做的事情就是給這三個(gè)私有Pod發(fā)版,發(fā)版之前去podspec里面確認(rèn)一下版本號(hào)和dependency萤悴。
Category的dependency是不需要填寫(xiě)對(duì)應(yīng)的業(yè)務(wù)線的瘾腰,它應(yīng)該是只依賴一個(gè)CTMediator
就可以了。其它業(yè)務(wù)線的dependency也是不需要依賴業(yè)務(wù)線的覆履,只需要依賴業(yè)務(wù)線的Category蹋盆。例如A業(yè)務(wù)線只需要依賴B_Category,而不需要依賴B業(yè)務(wù)線或主工程硝全。
發(fā)版過(guò)程就是幾行命令:
git add .
git commit -m "版本號(hào)"
git tag 版本號(hào)
git push origin master --tags
./upload.sh
命令行cd進(jìn)入到對(duì)應(yīng)的項(xiàng)目中栖雾,然后執(zhí)行以上命令就可以了。
要注意的是柳沙,這里的版本號(hào)
要和podspec文件中的s.version
給到的版本號(hào)一致岩灭。upload.sh
是配置私有Pod的腳本生成的,如果你這邊沒(méi)有upload.sh
這個(gè)文件赂鲤,說(shuō)明這個(gè)私有Pod你還沒(méi)用腳本配置過(guò)噪径。
最后,所有的Pod發(fā)完版之后数初,我們?cè)侔裀odfile里原來(lái)的本地引用改回正常引用找爱,也就是把:path...
那一段從Podfile里面去掉就好了,改動(dòng)之后記得commit并push泡孩。
組件化實(shí)施就這么三步车摄,到此結(jié)束。
總結(jié)
hard code
這個(gè)組件化方案的hard code僅存在于Target對(duì)象和Category方法中仑鸥,影響面極小吮播,并不會(huì)泄漏到主工程的業(yè)務(wù)代碼中,也不會(huì)泄漏到業(yè)務(wù)線的業(yè)務(wù)代碼中眼俊。
而且在實(shí)際組件化的實(shí)施中意狠,也是依據(jù)category去做業(yè)務(wù)線的組件化的。所以先寫(xiě)category里的target名字疮胖,action名字环戈,param參數(shù)闷板,到后面在業(yè)務(wù)線組件中創(chuàng)建Target的時(shí)候,照著category里面已經(jīng)寫(xiě)好的內(nèi)容直接copy到Target對(duì)象中就肯定不會(huì)出錯(cuò)(僅Target對(duì)象院塞,并不會(huì)牽扯到業(yè)務(wù)線本身原有的對(duì)象)遮晚。
如果要消除這一層hard code,那么勢(shì)必就要引入一個(gè)第三方pod拦止,然后target對(duì)象所在的業(yè)務(wù)線和category都要依賴這個(gè)pod县遣。為了消除這種影響面極小的hard code,而且只要按照章法來(lái)就不會(huì)出錯(cuò)汹族。為此引入一個(gè)新的依賴艺玲,其實(shí)是不劃算的。
命名域問(wèn)題
在這個(gè)實(shí)踐中鞠抑,響應(yīng)者的命名域并沒(méi)有泄漏到除了響應(yīng)者以外的任何地方,這就帶來(lái)一個(gè)好處忌警,遷移非常方便搁拙。
比如我們的響應(yīng)者是一個(gè)上傳組件。這個(gè)上傳組件如果要替換的話法绵,只需要在它外面包一個(gè)Target-Action箕速,就可以直接拿來(lái)用了。而且包Target-Action的過(guò)程中朋譬,不會(huì)產(chǎn)生任何侵入性的影響盐茎。
例如原來(lái)是你自己基于AFNetworking寫(xiě)的上傳組件,現(xiàn)在用了七牛SDK上傳徙赢,那么整個(gè)過(guò)程你只需要提供一個(gè)Target-Action封裝一下七牛的上傳操作即可字柠。不需要改動(dòng)七牛SDK的代碼,也不需要改動(dòng)調(diào)用方的代碼狡赐。倘若是基于URL注冊(cè)的調(diào)度窑业,做這個(gè)事情就很蛋疼。
服務(wù)管理問(wèn)題
由于Target對(duì)象處于響應(yīng)者的命名域中枕屉,Target對(duì)象就可以對(duì)外提供除了頁(yè)面實(shí)例以外的各種Action常柄。
而且,由于其本質(zhì)就是針對(duì)響應(yīng)者對(duì)外業(yè)務(wù)邏輯的Action化封裝(其實(shí)就是服務(wù)化封裝)搀擂,這就能夠使得一個(gè)響應(yīng)者對(duì)外提供了哪些Action(服務(wù))
西潘,Action(服務(wù))的實(shí)現(xiàn)邏輯是什么
得到了非常好的管理,能夠大大降低將來(lái)工程的維護(hù)成本哨颂。然后Category解決了服務(wù)應(yīng)該怎么調(diào)用
的問(wèn)題喷市。
但在基于URL注冊(cè)機(jī)制和Protocol共享機(jī)制的組件化方案中,由于服務(wù)散落在響應(yīng)者各處咆蒿,服務(wù)管理就顯得十分困難东抹。如果還是執(zhí)念于這樣的方案蚂子,大家只要拿上面提到的三個(gè)問(wèn)題,對(duì)照著URL注冊(cè)機(jī)制和Protocol共享機(jī)制的組件化方案比對(duì)一下缭黔,就能明白了食茎。
另外,如果這種方案把所有的服務(wù)歸攏到一個(gè)對(duì)象中來(lái)達(dá)到方便管理的目的的話馏谨,其本質(zhì)就已經(jīng)變成了Target-Action模式别渔,Protocol共享機(jī)制其實(shí)就已經(jīng)沒(méi)有存在意義了。
高內(nèi)聚
基于protocol共享機(jī)制的組件化方案導(dǎo)致響應(yīng)者業(yè)務(wù)邏輯泄漏到了調(diào)用者業(yè)務(wù)邏輯中惧互,并沒(méi)有做到高內(nèi)聚
哎媚。
如果這部分業(yè)務(wù)在其他地方也要使用,那么代碼就要重新寫(xiě)一遍喊儡。雖然它可以提供一個(gè)業(yè)務(wù)高內(nèi)聚的對(duì)象來(lái)符合這個(gè)protocol拨与,但事實(shí)上這就又變成了Target-Action模式,protocol的存在意義就也沒(méi)有了艾猜。
侵入性問(wèn)題
正如你所見(jiàn)买喧,CTMediator組件化方案的實(shí)施非常安全。因?yàn)樗⒉淮嬖谌魏吻秩胄缘拇a修改匆赃。
對(duì)于響應(yīng)者來(lái)說(shuō)淤毛,什么代碼都不用改,只需要包一層Target-Action即可算柳。例如本例中的B業(yè)務(wù)線作為A業(yè)務(wù)的響應(yīng)者時(shí)低淡,不需要修改B業(yè)務(wù)的任何代碼。
對(duì)于調(diào)用者來(lái)說(shuō)瞬项,只需要把調(diào)用方式換成CTMediator調(diào)用即可蔗蹋,其改動(dòng)也不涉及原有的業(yè)務(wù)邏輯,所以是十分安全的囱淋。
另外一個(gè)非侵入性的特征體現(xiàn)在纸颜,基于CTMediator的組件化方案是可以循序漸進(jìn)地實(shí)施的。這個(gè)方案的實(shí)施并不要求所有業(yè)務(wù)線都要被獨(dú)立出來(lái)成為組件绎橘,實(shí)施過(guò)程也并不會(huì)修改未組件化的業(yè)務(wù)的代碼胁孙。
在獨(dú)立A業(yè)務(wù)線的過(guò)程中如果涉及其它業(yè)務(wù)線(B業(yè)務(wù)線)的調(diào)用,就只需要給到Target對(duì)象即可称鳞,Target對(duì)象本身并不會(huì)對(duì)未組件化的業(yè)務(wù)線(B業(yè)務(wù)線)產(chǎn)生任何的修改涮较。而且將來(lái)如果對(duì)應(yīng)業(yè)務(wù)線需要被獨(dú)立出去的時(shí)候,也僅需要把Target對(duì)象一起復(fù)制過(guò)去就可以了冈止。
但在基于URL注冊(cè)和protocol共享的組件化方案中狂票,都必須要在未組件化的業(yè)務(wù)線中寫(xiě)入注冊(cè)代碼和protocol聲明,并分配對(duì)應(yīng)的URL和protocol到具體的業(yè)務(wù)對(duì)象上熙暴。這些其實(shí)都是不必要的闺属,無(wú)端多出了額外維護(hù)成本慌盯。
注冊(cè)問(wèn)題
CTMediator沒(méi)有任何注冊(cè)邏輯的代碼,避免了注冊(cè)文件的維護(hù)和管理掂器。Category給到的方法很明確地告知了調(diào)用者應(yīng)該如何調(diào)用亚皂。
例如B_Category給到的- (UIViewController *)B_viewControllerWithContentText:(NSString *)contentText;
方法。這能夠讓工程師一眼就能夠明白使用方式国瓮,而不必抓瞎拿著URL再去翻文檔灭必。
這可以很大程度提高工作效率,同時(shí)降低維護(hù)成本乃摹。
實(shí)施組件化方案的時(shí)機(jī)
MVP階段過(guò)后禁漓,越早實(shí)施越好。
這里說(shuō)的MVP不是一種設(shè)計(jì)模式孵睬,而是最小價(jià)值產(chǎn)品的意思播歼,它是產(chǎn)品演進(jìn)的第一個(gè)階段。
一般來(lái)說(shuō)天使輪就是用于MVP驗(yàn)證的掰读,在這個(gè)階段產(chǎn)品閉環(huán)尚未確定荚恶,因此產(chǎn)品本身的邏輯就會(huì)各種變化。但是過(guò)了天使輪之后磷支,產(chǎn)品閉環(huán)已經(jīng)確定,此時(shí)就應(yīng)當(dāng)實(shí)施組件化食寡,以應(yīng)對(duì)A輪之后的產(chǎn)品拓張雾狈。
有的人說(shuō)我現(xiàn)在項(xiàng)目很小,人也很少抵皱,所以沒(méi)必要實(shí)施組件化善榛。確實(shí),把一個(gè)小項(xiàng)目組件化之后呻畸,跟之前相比并沒(méi)有多大程度的改善移盆,因?yàn)楸緛?lái)小項(xiàng)目就不復(fù)雜,改成組件化之后伤为,也不會(huì)更簡(jiǎn)單咒循。
但這其實(shí)是一種很短視的認(rèn)知。
組件化對(duì)于一個(gè)小項(xiàng)目而言绞愚,真正發(fā)揮優(yōu)勢(shì)的地方是在未來(lái)的半年甚至一年之后叙甸。
因?yàn)槌弥松夙?xiàng)目小,實(shí)施組件化的成本就也很小位衩,三四天就可以實(shí)施完畢裆蒸。于是等將來(lái)一年之后業(yè)務(wù)拓張到更大規(guī)模時(shí),就不會(huì)束手束腳了糖驴。
但如果等到項(xiàng)目大了僚祷,人手多了再去實(shí)施組件化佛致,那時(shí)候?qū)嵤┙M件化的復(fù)雜度肯定比現(xiàn)在規(guī)模還很小的時(shí)候的復(fù)雜度要大得多,三四天肯定搞不定辙谜,而且實(shí)施過(guò)程還會(huì)非常艱辛俺榆。到那時(shí)你就后悔為什么當(dāng)初沒(méi)有早早實(shí)施組件化了。
Swift工程怎么辦筷弦?
其實(shí)只要Target對(duì)象繼承自NSObject就好了肋演,然后帶上@objc(className)。action的參數(shù)名永遠(yuǎn)只有一個(gè)烂琴,且名字需要固定為params
爹殊,其它照舊。具體swift工程中target的寫(xiě)法參見(jiàn)A_swift
因?yàn)門(mén)arget對(duì)象是游離于業(yè)務(wù)實(shí)現(xiàn)的奸绷,所以它去繼承NSObject完全沒(méi)有任何問(wèn)題梗夸。完整的SwiftDemo在這里。
本文Demo
評(píng)論系統(tǒng)我用的是Disqus号醉,不定期被墻反症。所以如果你看到文章下面沒(méi)有加載出評(píng)論列表,翻個(gè)墻就有了畔派。
本文遵守CC-BY铅碍。 請(qǐng)保持轉(zhuǎn)載后文章內(nèi)容的完整,以及文章出處线椰。本人保留所有版權(quán)相關(guān)權(quán)利胞谈。