使用CTMediators實現(xiàn)組件化探索

1.組件化基本分層

  • 組件化的分層思想是三層

    • 基礎(chǔ)模塊
    • 通用模塊
    • 業(yè)務(wù)模塊
  • 模塊的集成順序是從下至上对室,從基礎(chǔ)模塊通用模塊業(yè)務(wù)模塊合冀,但是依賴的順序是從上到下怖喻,業(yè)務(wù) 依賴 通用通用 依賴 基礎(chǔ)

  • 一二層模塊可以看做是同一個層次宪哩,也可以是兩個層次,但是即便是同一個層次第晰,也要劃分為兩個目錄

2.業(yè)務(wù)模塊

  1. 通常項目模塊間的關(guān)系大致如下圖


  • 各個模塊多少都有關(guān)系锁孟,模塊間進行通訊彬祖,需要導(dǎo)入彼此
  • 模塊間進行組件化首先要解決模的問題就是模塊間之間的耦合關(guān)系,基本的思路是建立中間層品抽,各個模塊之間通過中間層進行通訊储笑。如下圖


3.CTMediator 通訊中間框架介紹

  • 框架地址 https://github.com/casatwy/CTMediator
  • 代碼結(jié)構(gòu)


  • 框架的基本思路是
    • 將每個模塊的業(yè)務(wù)調(diào)用隔離出一個獨立Target(這個和OC的target-action模式?jīng)]有任何關(guān)系)層,該層作為該模塊通訊的入口圆恤,通俗的講就是這個模塊的聲明文件突倍,對該模塊的方法調(diào)用入口進行一次封裝
    • Target
    • 在代碼封裝過程可以進行一些業(yè)務(wù)層面的判斷或者容錯
  • 框架代碼思路
    • 將方法的調(diào)用轉(zhuǎn)換為方法的調(diào)用轉(zhuǎn)換為不導(dǎo)入某模塊的情況下去調(diào)用該模塊的方法,避免模塊間的導(dǎo)入
    • 以類名盆昙,方法名字符串形式進行調(diào)用羽历,通過方法簽名 + NSInvocation 方式對方法調(diào)用進行封裝,通過performSelector 進行調(diào)用
    • 對于參數(shù)淡喜,target, action等進行容錯處理

4.增加分類

image.png

4.1總體介紹

盡管CTMediator的代碼整體比較少秕磷,邏輯也不是很復(fù)雜,但是由于是避免對三方框架強耦合炼团,也避免框架的代碼發(fā)生過大的變化澎嚣,另外增加分類也是為了進一步解耦合,總結(jié)一下大致為以下幾點

  • 避免項目或者模塊對CTMediator框架進行強耦合
  • 防止CTMediator框架代碼出現(xiàn)比較大的升級或者停止維護等
  • 對模塊增加一層隔離瘟芝,每個模塊需要暴露給外部調(diào)用的都寫在分類里易桃,如果想調(diào)用該模塊的方法,進導(dǎo)入該模塊對應(yīng)的分類即可模狭。
    • 比如登錄模塊A颈抚,有登錄方法,注冊方法嚼鹉,忘記密碼方法贩汉,如果C在某個業(yè)務(wù)需要時需要登錄,這時只需要引入A分類即可

4.2 CTMediator代碼 和 demo介紹

下面我們針對在github給出的demo進行介紹

1. 項目整體介紹
  • 項目代碼結(jié)構(gòu)


  • 代碼運行起來锚赤,是一個tableView


  • 我們以 present image這個操作
2. 這個功能是將 DemoModuleADetailViewController modal出來匹舞,并需要往這個控制器里傳遞一個UIImage,DemoModuleADetailViewController 一下簡稱為模塊A
  • 模塊A 聲明暴露了一個imageView,用于賦值
@interface DemoModuleADetailViewController : UIViewController
@property (nonatomic, strong, readonly) UILabel *valueLabel;
@property (nonatomic, strong, readonly) UIImageView *imageView;
@end
  • 模塊A 的target 層 Target_A類對應(yīng)的方法及其實現(xiàn)為如下线脚,主要是對參數(shù)解析出來赐稽,并進行賦值
- (id)Action_nativePresentImage:(NSDictionary *)params
{
    DemoModuleADetailViewController *viewController = [[DemoModuleADetailViewController alloc] init];
    viewController.valueLabel.text = @"this is image";
    viewController.imageView.image = params[@"image"];
    [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:viewController animated:YES completion:nil];
    return nil;
}
  • 方法名 Action_nativePresentImage根據(jù)是根據(jù)目前CTM的規(guī)則拼接出來,方法名的規(guī)則是Action_ 拼上方法名浑侥,nativePresentImage是我們可以定義的方法的名字姊舵,這名字在分類的層面進行聲明拼接,在demo是定義成了一個static string.我的理解起名和如何定義字符串寓落,只要項目內(nèi)部約定好就行括丁,大家都根據(jù)這規(guī)則就好。
3. 第二步解決了方法名的問題伶选,然后我們整體看下方法的調(diào)用史飞,從cell的點擊一步一步
  • cell 點擊尖昏,調(diào)用CTM分類方法


  • 分類方法的實現(xiàn)


  • 調(diào)用到CTM內(nèi)部方法,進行類 和 方法名的轉(zhuǎn)換构资,創(chuàng)建對象抽诉,內(nèi)部應(yīng)做了相應(yīng)的注釋,下一步對 performSelector: 進一步封裝
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
{
    // tagrt判空
    if (targetName == nil || actionName == nil) {
        return nil;
    }
    // 對swift的特殊標(biāo)記
    NSString *swiftModuleName = params[kCTMediatorParamsKeySwiftTargetModuleName];
    
    // 拼接target-action的tagert,就是方法調(diào)用的類名的字符串吐绵,類名規(guī)則為Target_方法名
    NSString *targetClassString = nil;
    if (swiftModuleName.length > 0) {
        targetClassString = [NSString stringWithFormat:@"%@.Target_%@", swiftModuleName, targetName];
    } else {
        // 類名規(guī)則為Target_方法名
        targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
    }
    
    // 做了類對象的緩存迹淌,避免多次創(chuàng)建對象
    NSObject *target = self.cachedTarget[targetClassString];
    // 類對象
    if (target == nil) {
        Class targetClass = NSClassFromString(targetClassString);
        target = [[targetClass alloc] init];
    }
    // 處理方法名字 拼接規(guī)則為 Action_方法名
    NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
    // 方法名轉(zhuǎn) SEL
    SEL action = NSSelectorFromString(actionString);
    // 容錯處理
    if (target == nil) {
        // 這里是處理無響應(yīng)請求的地方之一,這個demo做得比較簡單拦赠,如果沒有可以響應(yīng)的target巍沙,就直接return了。實際開發(fā)過程中是可以事先給一個固定的target專門用于在這個時候頂上荷鼠,然后處理這種請求的
        [self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
        return nil;
    }
    // 緩存對象
    if (shouldCacheTarget) {
        self.cachedTarget[targetClassString] = target;
    }

    if ([target respondsToSelector:action]) {
        // 底層方法調(diào)用
        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都沒有的時候矮嫉,這個demo是直接return了。實際開發(fā)過程中牍疏,可以用前面提到的固定的target頂上的蠢笋。
            [self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
            [self.cachedTarget removeObjectForKey:targetClassString];
            return nil;
        }
    }
}
  • 此部分主要是對參數(shù)進行容錯,對performSelector進一步封裝鳞陨,并返回方法調(diào)用方的對象
- (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);
    }

    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);
    }

    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);
    }

    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
}

至此一個完整CTM調(diào)用就結(jié)束了昨寞,大致的流程為


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市厦滤,隨后出現(xiàn)的幾起案子援岩,更是在濱河造成了極大的恐慌,老刑警劉巖掏导,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件享怀,死亡現(xiàn)場離奇詭異,居然都是意外死亡趟咆,警方通過查閱死者的電腦和手機添瓷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來值纱,“玉大人鳞贷,你說我怎么就攤上這事∨斑耄” “怎么了悄晃?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我妈橄,道長,這世上最難降的妖魔是什么翁脆? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任眷蚓,我火速辦了婚禮,結(jié)果婚禮上反番,老公的妹妹穿的比我還像新娘沙热。我一直安慰自己,他們只是感情好罢缸,可當(dāng)我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布篙贸。 她就那樣靜靜地躺著,像睡著了一般枫疆。 火紅的嫁衣襯著肌膚如雪爵川。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天皿伺,我揣著相機與錄音急鳄,去河邊找鬼唬渗。 笑死,一個胖子當(dāng)著我的面吹牛圃泡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播愿险,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼颇蜡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了辆亏?” 一聲冷哼從身側(cè)響起风秤,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎褒链,沒想到半個月后唁情,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡甫匹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年甸鸟,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片兵迅。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡抢韭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出恍箭,到底是詐尸還是另有隱情刻恭,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站鳍贾,受9級特大地震影響鞍匾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜骑科,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一橡淑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧咆爽,春花似錦梁棠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至呛凶,卻和暖如春男娄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背把兔。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工沪伙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人县好。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓围橡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親缕贡。 傳聞我的和親對象是個殘疾皇子翁授,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,685評論 2 360