[iOS] 組件化方案學(xué)習(xí) - CTMediator

1. Target-Action

這種方案是基于 OCruntime碍遍、category 特性動態(tài)獲取模塊匣摘,例如通過 NSClassFromString 獲取類并創(chuàng)建實(shí)例蕊梧,通過 performSelector + NSInvocation動態(tài)調(diào)用方法压固。

這種方案的代表框架:CTMediator巩踏,實(shí)現(xiàn)原理可以看這篇文章秃诵,具體實(shí)踐可以看這篇文章

本篇文章主要就是通過上面提到的兩篇文章學(xué)習(xí)使用并記錄一下CTMediator這個(gè)框架,然后分析一下其在組件化實(shí)施中的優(yōu)點(diǎn)和缺點(diǎn)塞琼。

2. CTMediator的實(shí)現(xiàn)

我們先來看一下該方案實(shí)現(xiàn)的架構(gòu)圖菠净,如下圖所示:

截屏2021-04-11 下午6.38.48.png

我們以 Module_A為例,打算將 Module_A抽離為單獨(dú)的組件彪杉,在使用CTMediator 此種方案時(shí)毅往,需要先給 CTMediator添加一個(gè)相應(yīng)的 CTMediator+A 的分類,這個(gè)分類里面包含了Module_A對外提供的接口派近,以及負(fù)責(zé)具體實(shí)現(xiàn)的 Target攀唯,并且使用performTarget:action方法來調(diào)用Target 的方法,示例如下:

NSString * const kCTMediatorTargetA = @"A";
NSString * const kCTMediatorActionAViewController = @"getAViewController";

@implementation CTMediator (A)

- (UIViewController *)CTMediator_AViewController
{  
    UIViewController *viewController = [self performTarget:kCTMediatorTargetA
                                                    action: kCTMediatorActionAViewController
                                                    params:@{@"key":@"value"}
                                         shouldCacheTarget:NO
                                        ];
    if ([viewController isKindOfClass:[UIViewController class]]) {
        // view controller 交付出去之后渴丸,可以由外界選擇是push還是present
        return viewController;
    } else {
        // 這里處理異常場景侯嘀,具體如何處理取決于產(chǎn)品
        return [[UIViewController alloc] init];
    }
}

我們可以看到上面的performTarget:action方法是 CTMediator 類中的,這個(gè)并不影響我們繼續(xù)學(xué)習(xí)谱轨,它其實(shí)是將 Target 字符串戒幔,用 NSClassFromString 反射方法,構(gòu)建了 Target 對應(yīng)類的實(shí)例對象土童,然后調(diào)用了 action 對應(yīng)的方法溪食。

我們繼續(xù)往下看Module_A對應(yīng)的 Target_A,這個(gè) Target_AModule_A有單向引用娜扇,負(fù)責(zé)對于Module_A暴露接口方法的具體實(shí)現(xiàn)错沃,比如上面的那個(gè)方法栅组,將會調(diào)用到Target_A中的getAViewController方法,如下:

#import "DemoModuleAViewController.h"

@implementation Target_A

- (UIViewController *)Action_getAViewController:(NSDictionary *)params
{
    // 因?yàn)閍ction是從屬于ModuleA的枢析,所以action直接可以使用ModuleA里的所有聲明
    DemoModuleAViewController *viewController = [[DemoModuleAViewController alloc] init];
    viewController.valueLabel.text = params[@"key"];
    return viewController;
}
@end

從而實(shí)現(xiàn)了對Module_A 中方法的調(diào)用玉掸。

CTMediator 主要是利用了Category 分類提供的特性,去調(diào)用組件對應(yīng)的Catetory中的的方法醒叁,進(jìn)而再去調(diào)用Category分類中指定的 Target 的 Action方法司浪,這一步是利用反射 + NSInvocation調(diào)用方法,下面源碼分析時(shí)會有介紹把沼。

3. CTMediator 源碼分析

CTMediator代碼很簡短啊易,也很容易理解,但這并不是我們要學(xué)習(xí)的重點(diǎn)饮睬,我們需要了解的是CTMediator這種實(shí)現(xiàn)方案的思想租谈,不同項(xiàng)目中對于組件之間通信的方案是不一樣的,這個(gè)就需要在實(shí)踐的時(shí)候具體去衡量了捆愁。

我們以上面提到的performTarget:action:params:方法為切入點(diǎn):

- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
{
    if (targetName == nil || actionName == nil) {
        return nil;
    }
    // 支持 Swift
    NSString *swiftModuleName = params[kCTMediatorParamsKeySwiftTargetModuleName];
    
    // 創(chuàng)建 Target 對應(yīng)的類名
    NSString *targetClassString = nil;
    if (swiftModuleName.length > 0) {
        targetClassString = [NSString stringWithFormat:@"%@.Target_%@", swiftModuleName, targetName];
    } else {
        targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
    }
    // 創(chuàng)建類的實(shí)例對象割去,這里為了效率,使用了緩存
    NSObject *target = [self safeFetchCachedTarget:targetClassString];
    if (target == nil) {
        Class targetClass = NSClassFromString(targetClassString);
        target = [[targetClass alloc] init];
    }

    // 獲取 action 對應(yīng)的方法
    NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
    SEL action = NSSelectorFromString(actionString);
    
    if (target == nil) {
        // 這里是處理無響應(yīng)請求的地方之一昼丑,這個(gè)demo做得比較簡單呻逆,如果沒有可以響應(yīng)的target,就直接return了菩帝。實(shí)際開發(fā)過程中是可以事先給一個(gè)固定的target專門用于在這個(gè)時(shí)候頂上咖城,然后處理這種請求的
        [self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
        return nil;
    }
    
    // 如果需要緩存的話,就將剛才創(chuàng)建的實(shí)例對象加入緩存
    if (shouldCacheTarget) {
        [self safeSetCachedTarget:target key:targetClassString];
    }
    
    // 去調(diào)用 target 實(shí)例對象的 action 方法
    if ([target respondsToSelector:action]) {
        return [self safePerformAction:action target:target params:params];
    } else {
        // 這里是處理無響應(yīng)請求的地方呼奢,如果無響應(yīng)掂榔,則嘗試調(diào)用對應(yīng)target的notFound方法統(tǒng)一處理
        SEL action = NSSelectorFromString(@"notFound:");
        if ([target respondsToSelector:action]) {
            return [self safePerformAction:action target:target params:params];
        } else {
            // 這里也是處理無響應(yīng)請求的地方弄兜,在notFound都沒有的時(shí)候,這個(gè)demo是直接return了。實(shí)際開發(fā)過程中调炬,可以用前面提到的固定的target頂上的乔夯。
            [self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
            @synchronized (self) {
                [self.cachedTarget removeObjectForKey:targetClassString];
            }
            return nil;
        }
    }
}

上面有對于方法或者類找不到的容錯(cuò)處理库说,這里就不做介紹了藏斩,該方法主要是通過 NSClassFromString 將傳入的target 轉(zhuǎn)成類,并創(chuàng)建一個(gè)該類的對象菇存,然后通過 NSSelectorFromString 將傳入的 action 轉(zhuǎn)成 方法夸研,然后調(diào)用targetaction 方法,調(diào)用的方法實(shí)現(xiàn)如下:

- (id)safePerformAction:(SEL)action target:(NSObject *)target params:(NSDictionary *)params
{
    NSMethodSignature* methodSig = [target methodSignatureForSelector:action];
    if(methodSig == nil) {
        return nil;
    }
    const char* retType = [methodSig methodReturnType];

    if (strcmp(retType, @encode(void)) == 0) {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
        [invocation setArgument:&params atIndex:2];
        [invocation setSelector:action];
        [invocation setTarget:target];
        [invocation invoke];
        return nil;
    }

    if (strcmp(retType, @encode(NSInteger)) == 0) {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
        [invocation setArgument:&params atIndex:2];
        [invocation setSelector:action];
        [invocation setTarget:target];
        [invocation invoke];
        NSInteger result = 0;
        [invocation getReturnValue:&result];
        return @(result);
    }
...
}

就是獲取 action 對應(yīng)的方法簽名依鸥,然后使用NSInvocation調(diào)用亥至, 或者使用系統(tǒng)的performSelector:withObject:調(diào)用方法。

4. 總結(jié)

優(yōu)點(diǎn):

  • 利用 category 可以明確聲明的接口,進(jìn)行編譯檢查
  • 實(shí)現(xiàn)方式屬于輕量級

缺點(diǎn):

  • 需要給每一個(gè)模塊創(chuàng)建對應(yīng)的 targetmediator 分類姐扮,模塊化代碼時(shí)較為繁瑣
  • category 中仍然使用了字符串硬編碼絮供,內(nèi)部使用字典傳參
  • 無法保證所調(diào)用的目標(biāo)模塊一定存在,運(yùn)行時(shí)才能發(fā)現(xiàn)錯(cuò)誤
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末茶敏,一起剝皮案震驚了整個(gè)濱河市壤靶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌惊搏,老刑警劉巖贮乳,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異恬惯,居然都是意外死亡向拆,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進(jìn)店門酪耳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來浓恳,“玉大人,你說我怎么就攤上這事葡兑〗甭” “怎么了赞草?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵讹堤,是天一觀的道長。 經(jīng)常有香客問我厨疙,道長洲守,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任沾凄,我火速辦了婚禮梗醇,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘撒蟀。我一直安慰自己叙谨,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布保屯。 她就那樣靜靜地躺著手负,像睡著了一般。 火紅的嫁衣襯著肌膚如雪姑尺。 梳的紋絲不亂的頭發(fā)上竟终,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天,我揣著相機(jī)與錄音切蟋,去河邊找鬼统捶。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的喘鸟。 我是一名探鬼主播匆绣,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼迷守!你這毒婦竟也來了犬绒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤兑凿,失蹤者是張志新(化名)和其女友劉穎凯力,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體礼华,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咐鹤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了圣絮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片祈惶。...
    茶點(diǎn)故事閱讀 40,444評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖扮匠,靈堂內(nèi)的尸體忽然破棺而出捧请,到底是詐尸還是另有隱情,我是刑警寧澤棒搜,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布疹蛉,位于F島的核電站,受9級特大地震影響力麸,放射性物質(zhì)發(fā)生泄漏可款。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一克蚂、第九天 我趴在偏房一處隱蔽的房頂上張望闺鲸。 院中可真熱鬧,春花似錦埃叭、人聲如沸摸恍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽立镶。三九已至,卻和暖如春益缎,著一層夾襖步出監(jiān)牢的瞬間谜慌,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工莺奔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留欣范,地道東北人变泄。 一個(gè)月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像恼琼,于是被迫代替她去往敵國和親妨蛹。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評論 2 359

推薦閱讀更多精彩內(nèi)容