CTMediator

基本概念

首先,對(duì)于 iOS 這種面向?qū)ο缶幊痰拈_發(fā)模式來(lái)說(shuō)拯坟,我們應(yīng)該遵循以下五個(gè)原則痢甘,即 SOLID 原則扣甲。

另外,組件可以認(rèn)為是可組裝的、獨(dú)立的業(yè)務(wù)單元,具有高內(nèi)聚斤儿,低耦合的特性励饵,是一種比較適中的粒度驳癌。就像用樂高拼房子一樣,每個(gè)對(duì)象就是一塊小積木役听。一個(gè)組件就是由一塊一塊的小積木組成的有單一功能的組合颓鲜,比如門表窘、柱子、煙囪甜滨。這讓我想到了viper設(shè)計(jì)模式

iOS 開發(fā)中的組件乐严,不是 UI 的控件,也不是 ViewController 這種大 UI 和功能的集合衣摩。因?yàn)榘貉椋琔I 控件的粒度太小,而頁(yè)面的粒度又太大艾扮。iOS 組件既琴,應(yīng)該是包含 UI 控件、相關(guān)多個(gè)小功能的合集泡嘴,是一種粒度適中的模塊甫恩。
而對(duì)于組件間如何分層這個(gè)問(wèn)題,層級(jí)最好不要超過(guò)三個(gè)酌予,你可以這么設(shè)置:

  • 底層可以是與業(yè)務(wù)無(wú)關(guān)的基礎(chǔ)組件磺箕,比如網(wǎng)絡(luò)和存儲(chǔ)等;
  • 中間層一般是通用的業(yè)務(wù)組件霎终,比如賬號(hào)滞磺、埋點(diǎn)、支付莱褒、購(gòu)物車等击困;
  • 最上層是迭代業(yè)務(wù)組件,更新頻率最高广凸。

在實(shí)踐中阅茶,,組件化的設(shè)計(jì)方法一般分為了協(xié)議式中間者兩種架構(gòu)設(shè)計(jì)方案

協(xié)議式架構(gòu)設(shè)計(jì)主要采用的是協(xié)議式編程的思路:在編譯層面使用協(xié)議定義規(guī)范,實(shí)現(xiàn)在不同地方谅海,從而達(dá)到分布管理和維護(hù)組件的目的脸哀。這種方式也遵循了依賴反轉(zhuǎn)原則,是一種很好的面向?qū)ο缶幊痰膶?shí)踐扭吁。
但是撞蜂,這個(gè)方案的缺點(diǎn)也很明顯,主要體現(xiàn)在以下兩個(gè)方面:

  1. 由于協(xié)議式編程缺少統(tǒng)一調(diào)度層侥袜,導(dǎo)致難于集中管理蝌诡,特別是項(xiàng)目規(guī)模變大、團(tuán)隊(duì)變多的情況下枫吧,架構(gòu)管控就會(huì)顯得越來(lái)越重要浦旱。
  2. 協(xié)議式編程接口定義模式過(guò)于規(guī)范,從而使得架構(gòu)的靈活性不夠高九杂。當(dāng)需要引入一個(gè)新的設(shè)計(jì)模式來(lái)開發(fā)時(shí)颁湖,我們就會(huì)發(fā)現(xiàn)很難融入到當(dāng)前架構(gòu)中宣蠕,缺乏架構(gòu)的統(tǒng)一性。

中間者架構(gòu)甥捺。它采用中間者統(tǒng)一管理的方式抢蚀,來(lái)控制 App 的整個(gè)生命周期中組件間的調(diào)用關(guān)系。同時(shí)涎永,iOS 對(duì)于組件接口的設(shè)計(jì)也需要保持一致性思币,方便中間者統(tǒng)一調(diào)用。

image.png

拆分的組件都會(huì)依賴于中間者羡微,但是組間之間就不存在相互依賴的關(guān)系了。由于其他組件都會(huì)依賴于這個(gè)中間者惶我,相互間的通信都會(huì)通過(guò)中間者統(tǒng)一調(diào)度妈倔,所以組件間的通信也就更容易管理了。在中間者上也能夠輕松添加新的設(shè)計(jì)模式绸贡,從而使得架構(gòu)更容易擴(kuò)展盯蝴。

好的架構(gòu)一定是健壯的、靈活的听怕。中間者架構(gòu)的易管控帶來(lái)的架構(gòu)更穩(wěn)固捧挺,易擴(kuò)展帶來(lái)的靈活性, CTMediator 就是按照中間者架構(gòu)思路設(shè)計(jì)的尿瞭。

CTMediator

CTMediator 使用的是運(yùn)行時(shí)解耦闽烙,解耦核心方法如下所示:

- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
{
    NSString *swiftModuleName = params[kCTMediatorParamsKeySwiftTargetModuleName];
    
    // 適配swift
    NSString *targetClassString = nil;
    if (swiftModuleName.length > 0) {
        targetClassString = [NSString stringWithFormat:@"%@.Target_%@", swiftModuleName, targetName];
    } else {
        targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
    }
    // 獲取緩存的target
    NSObject *target = self.cachedTarget[targetClassString];
    if (target == nil) {
        // 沒有緩存則創(chuàng)建
        Class targetClass = NSClassFromString(targetClassString);
        target = [[targetClass alloc] init];
    }


    // 創(chuàng)建消息
    NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
    SEL action = NSSelectorFromString(actionString);
    
    if (target == nil) {
        // 這里是處理無(wú)響應(yīng)請(qǐng)求的地方之一
        [self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
        return nil;
    }
    
    // 緩存target
    if (shouldCacheTarget) {
        self.cachedTarget[targetClassString] = target;
    }


    if ([target respondsToSelector:action]) {
      // 發(fā)送請(qǐng)求
        return [self safePerformAction:action target:target params:params];
    } else {
        // 這里是處理無(wú)響應(yīng)請(qǐng)求的地方,如果無(wú)響應(yīng)声搁,則嘗試調(diào)用對(duì)應(yīng)target的notFound方法統(tǒng)一處理
        SEL action = NSSelectorFromString(@"notFound:");
        if ([target respondsToSelector:action]) {
            return [self safePerformAction:action target:target params:params];
        } else {
            // 這里也是處理無(wú)響應(yīng)請(qǐng)求的地方黑竞,在notFound都沒有的時(shí)候,
            [self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
            // 移除緩存
            [self.cachedTarget removeObjectForKey:targetClassString];
            return nil;
        }
    }

performTarget:action:params:shouldCacheTarget:方法主要是對(duì) targetName 和 actionName 進(jìn)行容錯(cuò)處理疏旨,也就是對(duì)調(diào)用方法無(wú)響應(yīng)的處理很魂。
這個(gè)方法封裝了safePerformAction:target:params 方法,入?yún)?targetName 就是調(diào)用接口的對(duì)象檐涝,actionName 是調(diào)用的方法名遏匆,params 是參數(shù)。

并且代碼中同時(shí)還能看出只有滿足Target_ 前綴的類的對(duì)象Action_ 的方法才能被 CTMediator 使用谁榜。這時(shí)幅聘,我們可以看出中間者架構(gòu)的優(yōu)勢(shì),也就是利于統(tǒng)一管理惰爬,可以輕松管控制定的規(guī)則喊暖。

處理無(wú)響應(yīng)的情況
#pragma mark - private methods
- (void)NoTargetActionResponseWithTargetString:(NSString *)targetString selectorString:(NSString *)selectorString originParams:(NSDictionary *)originParams
{
    // 給一個(gè)固定的target專門用于在這個(gè)時(shí)候頂上,處理這種請(qǐng)求的
    SEL action = NSSelectorFromString(@"Action_response:");
    NSObject *target = [[NSClassFromString(@"Target_NoTargetAction") alloc] init];
    
    NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
    params[@"originParams"] = originParams;
    params[@"targetString"] = targetString;
    params[@"selectorString"] = selectorString;
    
    [self safePerformAction:action target:target params:params];
}
處理有action的情況
- (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];
    
    // 對(duì)非對(duì)象的返回類型做一次封裝
    
    // 如果返回值類型是 void
    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);
    }

    // 如果是bool類型
    if (strcmp(retType, @encode(BOOL)) == 0) {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
        [invocation setArgument:&params atIndex:2];
        [invocation setSelector:action];
        [invocation setTarget:target];
        [invocation invoke];
        BOOL result = 0;
        [invocation getReturnValue:&result];
        return @(result);
    }

    // 如果是浮點(diǎn)型
    if (strcmp(retType, @encode(CGFloat)) == 0) {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
        [invocation setArgument:&params atIndex:2];
        [invocation setSelector:action];
        [invocation setTarget:target];
        [invocation invoke];
        CGFloat result = 0;
        [invocation getReturnValue:&result];
        return @(result);
    }

    // 如果是無(wú)符號(hào)整型
    if (strcmp(retType, @encode(NSUInteger)) == 0) {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
        [invocation setArgument:&params atIndex:2];
        [invocation setSelector:action];
        [invocation setTarget:target];
        [invocation invoke];
        NSUInteger result = 0;
        [invocation getReturnValue:&result];
        return @(result);
    }

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    return [target performSelector:action withObject:params];
#pragma clang diagnostic pop
}

簡(jiǎn)單實(shí)例

彈出一個(gè)Alert

[[CTMediator sharedInstance] CTMediator_showAlertWithMessage:@"casa" cancelAction:nil confirmAction:^(NSDictionary *info) {
            // 做你想做的事
        }];

內(nèi)部實(shí)現(xiàn)

NSString * const kCTMediatorTargetA = @"A";
NSString * const kCTMediatorActionShowAlert = @"showAlert";

- (void)CTMediator_showAlertWithMessage:(NSString *)message cancelAction:(void(^)(NSDictionary *info))cancelAction confirmAction:(void(^)(NSDictionary *info))confirmAction
{
    NSMutableDictionary *paramsToSend = [[NSMutableDictionary alloc] init];
    if (message) {
        paramsToSend[@"message"] = message;
    }
    if (cancelAction) {
        paramsToSend[@"cancelAction"] = cancelAction;
    }
    if (confirmAction) {
        paramsToSend[@"confirmAction"] = confirmAction;
    }
    [self performTarget:kCTMediatorTargetA
                 action:kCTMediatorActionShowAlert
                 params:paramsToSend
      shouldCacheTarget:NO];
}
@interface Target_A : NSObject
- (id)Action_showAlert:(NSDictionary *)params;
@end
@implementation Target_A
- (id)Action_showAlert:(NSDictionary *)params
{
    UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        CTUrlRouterCallbackBlock callback = params[@"cancelAction"];
        if (callback) {
            callback(@{@"alertAction":action});
        }
    }];
    
    UIAlertAction *confirmAction = [UIAlertAction actionWithTitle:@"confirm" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        CTUrlRouterCallbackBlock callback = params[@"confirmAction"];
        if (callback) {
            callback(@{@"alertAction":action});
        }
    }];
    
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"alert from Module A" message:params[@"message"] preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:cancelAction];
    [alertController addAction:confirmAction];
    [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alertController animated:YES completion:nil];
    return nil;
}
@end

可以看出撕瞧,指定了類名和調(diào)用方法名陵叽,把參數(shù)封裝成字典傳進(jìn)去就能夠直接調(diào)用該方法了狞尔。
但是,這種運(yùn)行時(shí)直接硬編碼的調(diào)用方式也有些缺點(diǎn)巩掺,主要表現(xiàn)在兩個(gè)方面:

  1. 直接硬編碼的調(diào)用方式偏序,參數(shù)是以 string 的方法保存在內(nèi)存里,雖然和將參數(shù)保存在 Text 字段里占用的內(nèi)存差不多胖替,同時(shí)還可以避免.h 文件的耦合研儒,但是其對(duì)代碼編寫效率的降低也比較明顯。
  2. 由于是在運(yùn)行時(shí)才確定的調(diào)用方法独令,調(diào)用方式由 [obj method] 變成 [obj performSelector:@""]端朵。這樣的話,在調(diào)用時(shí)就缺少類型檢查燃箭,是個(gè)很大的缺憾冲呢。因?yàn)椋绻椒ê蛥?shù)比較多的時(shí)候招狸,代碼編寫效率就會(huì)比較低敬拓。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市裙戏,隨后出現(xiàn)的幾起案子乘凸,更是在濱河造成了極大的恐慌,老刑警劉巖累榜,帶你破解...
    沈念sama閱讀 216,692評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件营勤,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡信柿,警方通過(guò)查閱死者的電腦和手機(jī)冀偶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)渔嚷,“玉大人进鸠,你說(shuō)我怎么就攤上這事⌒尾。” “怎么了客年?”我有些...
    開封第一講書人閱讀 162,995評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)漠吻。 經(jīng)常有香客問(wèn)我量瓜,道長(zhǎng),這世上最難降的妖魔是什么途乃? 我笑而不...
    開封第一講書人閱讀 58,223評(píng)論 1 292
  • 正文 為了忘掉前任绍傲,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘烫饼。我一直安慰自己猎塞,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評(píng)論 6 388
  • 文/花漫 我一把揭開白布杠纵。 她就那樣靜靜地躺著荠耽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪比藻。 梳的紋絲不亂的頭發(fā)上铝量,一...
    開封第一講書人閱讀 51,208評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音银亲,去河邊找鬼慢叨。 笑死,一個(gè)胖子當(dāng)著我的面吹牛群凶,可吹牛的內(nèi)容都是我干的插爹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,091評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼请梢,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了力穗?” 一聲冷哼從身側(cè)響起毅弧,我...
    開封第一講書人閱讀 38,929評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎当窗,沒想到半個(gè)月后够坐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,346評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡崖面,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評(píng)論 2 333
  • 正文 我和宋清朗相戀三年元咙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片巫员。...
    茶點(diǎn)故事閱讀 39,739評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡庶香,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出简识,到底是詐尸還是另有隱情赶掖,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評(píng)論 5 344
  • 正文 年R本政府宣布七扰,位于F島的核電站奢赂,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏颈走。R本人自食惡果不足惜膳灶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望立由。 院中可真熱鬧轧钓,春花似錦序厉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春厨内,著一層夾襖步出監(jiān)牢的瞬間裙秋,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工拄轻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人伟葫。 一個(gè)月前我還...
    沈念sama閱讀 47,760評(píng)論 2 369
  • 正文 我出身青樓恨搓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親筏养。 傳聞我的和親對(duì)象是個(gè)殘疾皇子斧抱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評(píng)論 2 354

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