本文的主要目的是分析CTMediator以及其使用
CTMediator簡(jiǎn)介
CTMediator是casatwy
開(kāi)源的一個(gè)三方組件通訊框架脸爱,下圖是通過(guò)CTMediator通訊的整體架構(gòu)圖示
優(yōu)點(diǎn):
組件僅通過(guò)Action暴露可調(diào)用接口魁淳,模塊與模塊之間的接口被固化在了Target-Action這一層,避免了實(shí)施組件化的改造過(guò)程中诊县,對(duì)Business的侵入,同時(shí)也提高了組件化接口的可維護(hù)性。
方便傳遞各種類(lèi)型的參數(shù)。
本地組件通訊為遠(yuǎn)程組件通訊提供服務(wù)
利用 category 可以明確聲明的接口哼勇,進(jìn)行編譯檢查
實(shí)現(xiàn)方式屬于輕量級(jí)
缺點(diǎn):
需要給每一個(gè)模塊創(chuàng)建對(duì)應(yīng)的 target和 mediator 分類(lèi),模塊化代碼時(shí)較為繁瑣
在 category 中仍然使用了字符串硬編碼呕乎,內(nèi)部使用字典傳參
無(wú)法保證所調(diào)用的目標(biāo)模塊一定存在猴蹂,運(yùn)行時(shí)才能發(fā)現(xiàn)錯(cuò)誤
CTMediator原理
解耦原理
通過(guò)
中介模式
,將CTMediator類(lèi)作為各個(gè)模塊的中心楣嘁,各個(gè)模塊以CTMediator分類(lèi)的形式擴(kuò)展功能,CTMediator分類(lèi)中提供服務(wù)分類(lèi)中的函數(shù)具體去調(diào)用target-action
,target和action需要以字符串硬編碼
的形式逐虚,如果模塊比較多聋溜,提供服務(wù)比較多,那么字符串的硬編碼需要時(shí)間維護(hù)叭爱。去model化思想
:如果A模塊向B模塊傳遞信息撮躁,推薦參數(shù)使用基本數(shù)據(jù)類(lèi)型,而不是以model形式提供买雾,否則A模塊依賴同一個(gè)model,B模塊也依賴同一個(gè)model把曼,這樣模塊間還是存在相互依賴,沒(méi)有達(dá)到真正完全耦合漓穿。
實(shí)現(xiàn)原理
- 基于OC的
runtime
嗤军、category
特性動(dòng)態(tài)獲取模塊通過(guò)
NSClassFromString
動(dòng)態(tài)獲取類(lèi),并創(chuàng)建實(shí)例通過(guò)
NSSelectorFromString
動(dòng)態(tài)獲取SEL
- 通過(guò)
performSelector+NSInvocation
動(dòng)態(tài)調(diào)用方法performSelector響應(yīng)OC的動(dòng)態(tài)性晃危,將
方法的綁定延遲到運(yùn)行時(shí)
叙赚,即在編譯階段不會(huì)檢測(cè)方法的有效性(如果方法不存在也不會(huì)報(bào)錯(cuò))。如果方法名未知可能會(huì)引起內(nèi)存泄漏相關(guān)問(wèn)題僚饭,可以通過(guò)以下代碼忽略此警告
//如果方法名稱(chēng)是動(dòng)態(tài)不確定的震叮,會(huì)有如下提示
?? PerformSelector may cause a leak because its selector is unknown
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self performSelector:selector];
#pragma clang diagnostic pop
performSelector
提供動(dòng)態(tài)執(zhí)行方法的能力
NSInvocation
提供了消息調(diào)用的能力
方法調(diào)用
的本質(zhì)是消息發(fā)送
,即底層調(diào)用的是objc_msgSend
鳍鸵,可以從objc源碼得到驗(yàn)證
- (id)performSelector:(SEL)sel withObject:(id)obj {
if (!sel) [self doesNotRecognizeSelector:sel];
return ((id(*)(id, SEL, id))objc_msgSend)(self, sel, obj);
}
源碼分析
通過(guò)閱讀CTMediator的源碼可知苇瓣,以下是源碼的通訊過(guò)程圖示
以上是組件化方案的一個(gè)簡(jiǎn)化版架構(gòu)描述,主要是基于Mediator模式
和Target-Action模式
偿乖,中間采用了runtime
來(lái)完成調(diào)用击罪。這套組件化方案將遠(yuǎn)程應(yīng)用調(diào)用和本地應(yīng)用調(diào)用做了拆分,而且是由本地應(yīng)用調(diào)用為遠(yuǎn)程應(yīng)用調(diào)用提供服務(wù)
CTMediator調(diào)用的方式有兩種(偽代碼實(shí)現(xiàn))
- 本地組件化通訊:本地原生模塊間的調(diào)用
- (id _Nullable )performTarget:action:params:shouldCacheTarget:{
//拼接類(lèi)名字符串
NSString *targetClassString = "Target_" + targetNaem;
//從緩存中獲取類(lèi)
NSObject *target = [self safeFetchCachedTarget:targetClassString]
if (緩存中沒(méi)有target){
Class targetClass = NSClassFromString(targetClassString);
target = [[targetClass alloc] init];
}
//拼接方法字符串
NSString *actionString = "Action_" + actionName;
//生成SEL
SEL action = NSSelectorFromString(actionString);
if (target為空){
處理target為空的情況
}
//是否緩存target
if (shouldCacheTarget) {
//以key-value的形式緩存
(key:target, value:targetClassString)
}
//判斷target是否響應(yīng)action
if ([target respondsToSelector:action]) {
return [self safePerformAction:action target:target params:params];
}else{
處理target未響應(yīng)action的情況
}
}
- (id)safePerformAction:target:params:{
//根據(jù)action生成簽名
NSMethodSignature* methodSig = [target methodSignatureForSelector:action];
//根據(jù)不同的返回類(lèi)型汹想,進(jìn)行封裝
//根據(jù)簽名對(duì)象創(chuàng)建調(diào)用對(duì)象invocation
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
invocation設(shè)置target外邓、action、params
//消息調(diào)用
[invocation invoke];
//提供動(dòng)態(tài)執(zhí)行方法
return [target performSelector:action withObject:params];
}
- 遠(yuǎn)程組件化通訊:其他app調(diào)用古掏,主要是通過(guò)openURL的方式损话,需要在AppDelegate的openUrl代理方法中進(jìn)行處理
- (id _Nullable)performActionWithUrl:completion:{
處理url參數(shù) - 遍歷并存放到字典中
處理targetName,目的是為了防止黑客通過(guò)遠(yuǎn)程方式調(diào)用本地模塊
[self performTarget:action:params:shouldCacheTarget:];
回調(diào)處理
}
CTMediator使用
傳統(tǒng)的界面跳轉(zhuǎn)方式如下槽唾,模塊間高度耦合
以下是以Home丧枪、Detail模塊為例,通過(guò)CTMediator的解耦方式
以下是Demo代碼的整體結(jié)構(gòu)
具體的實(shí)現(xiàn)代碼如下
- Home模塊實(shí)現(xiàn)的核心代碼如下
- (void)pushDetailAction:(UIButton *)sender{
UIViewController *vc = [MIM() MIMediator_pushDetail];
[self.navigationController pushViewController:vc animated:YES];
}
- Detail模塊實(shí)現(xiàn)的核心代碼如下
- (void)showAlert{
// [[MIMediator sharedInstance] MIMediator_showAlert];
[MIM() MIMediator_showAlert];
}
可以簡(jiǎn)化組件化單例調(diào)用方式
// 簡(jiǎn)化調(diào)用單例的函數(shù)
MIMediator* _Nonnull MIMD(void);
- 組件化通訊實(shí)現(xiàn)的代碼如下
//MIMediator+Universal中的實(shí)現(xiàn)
- (UIViewController *)MIMediator_pushDetail{
UIViewController *vc = [self performTarget:kMIMediatorTargetDetail action:kMIMediatorActionPushDetail params:nil shouldCacheTarget:NO];
if ([vc isKindOfClass:[UIViewController class]]) {
return vc;
}else{
return [[UIViewController alloc] init];;
}
}
- (void)MIMediator_showAlert{
[self performTarget:kMIMediatorTargetDetail action:kMIMediatorActionShowAlert params:nil shouldCacheTarget:NO];
}
//Target_Detail中的實(shí)現(xiàn)
- (UIViewController *)Action_pushToDetail:(NSDictionary *)param{
DetailViewController *detailVC = [[DetailViewController alloc] init];
detailVC.title = @"詳情頁(yè)";
return detailVC;
}
- (id)Action_showAlert:(NSDictionary *)dic{
UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:@"alert title" message:@"alert message" preferredStyle:UIAlertControllerStyleAlert];
[alertVC addAction:[UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleCancel handler:nil]];
[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alertVC animated:YES completion:nil];
return nil;
}
參考鏈接: