CTMediator源碼閱讀和實際使用

iOS組件化CTMediator代碼閱讀及實際項目使用

前言

當(dāng)項目代碼量越來越大,團隊人數(shù)越來越多,單一工程的開發(fā)方式漸漸成為開發(fā)效率的掣肘。此時就是應(yīng)該引入組件化的時候。

組件化的最大難題我認(rèn)為是在組件抽離的粒度,抽離的粒度直接關(guān)系到了組件化是否能提高開發(fā)效率,或者說起反作用。

公司項目中使用的組件化方案是基于CTMediatortarget-action方式,利用runtime動態(tài)生成組件類的對象實現(xiàn)解耦,CTMediator本身代碼也只有200行十分的好理解。

大致流程

1573562135191.jpg

源碼閱讀

當(dāng)我們希望跳轉(zhuǎn)到模塊A的ModuleAViewController時引入對應(yīng)模塊的CTMediator類擴展

#import "CTMediator+ModuleA.h"
- (IBAction)action:(id)sender {
    UIViewController* moduleA = [[CTMediator sharedInstance] Mediator_ModuleAViewController];
    [self.navigationController pushViewController:moduleA animated:YES];
}

類擴展內(nèi)部調(diào)用CTMediatorperformTarget:action:shouldCacheTarget(這命名十分的apple)方法

傳入模塊名和action的名稱 例:

- (UIViewController*)Mediator_ModuleAViewController {
    UIViewController* vc = [self performTarget:@"ModuleA" action:@"ModuleAViewController" params:@{} shouldCacheTarget:NO];
    return vc;
}

CTMediator.m中的performTarget:ation:params:shouldCacheTarget方法的實現(xiàn)

- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
{
    NSString *swiftModuleName = params[kCTMediatorParamsKeySwiftTargetModuleName];
    
    // generate target
    NSString *targetClassString = nil;
    if (swiftModuleName.length > 0) {
        targetClassString = [NSString stringWithFormat:@"%@.Target_%@", swiftModuleName, targetName];
    } else {
        //根據(jù)傳入的targetName去拼接對應(yīng)模塊的入口類名 規(guī)則為Target_{ModuleName}
        //注意對應(yīng)模塊入口類的命名
        targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
    }
    //判斷對應(yīng)的類是否已經(jīng)在緩存中 如果在緩存中則不反復(fù)的生成類對象
    NSObject *target = self.cachedTarget[targetClassString];
    if (target == nil) {
        //緩存中不存在 根據(jù)類名獲取class 再init出來類對象
        Class targetClass = NSClassFromString(targetClassString);
        target = [[targetClass alloc] init];
    }

    // 拼接方法名 注意規(guī)則
    NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
    SEL action = NSSelectorFromString(actionString);//根據(jù)方法名生成方法的SEL對象
    
    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;
    }
    //判斷對象是否實現(xiàn)了對應(yīng)的action方法 實現(xiàn)了則通過safePerformAction調(diào)用
    if ([target respondsToSelector:action]) {
        return [self safePerformAction:action target:target params:params];
    } else {
        // 對象沒有實現(xiàn)對應(yīng)的action 判斷是否實現(xiàn)了統(tǒng)一處理異常的notFound方法
        SEL action = NSSelectorFromString(@"notFound:");
        if ([target respondsToSelector:action]) {
            //實現(xiàn)了則調(diào)用notFound方法
            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;
        }
    }
}

CTMediator.m中的safePerformAction:target:params方法的實現(xiàn)

- (id)safePerformAction:(SEL)action target:(NSObject *)target params:(NSDictionary *)params
{
    //根據(jù)傳入的類對象和方法的SEL對象 獲取到方法的簽名(方法的返回類型,有什么參數(shù))
    NSMethodSignature* methodSig = [target methodSignatureForSelector:action];
    //簽名為nil直接返回
    if(methodSig == nil) {
        return nil;
    }
    const char* retType = [methodSig methodReturnType];
    //無返回值和返回值為數(shù)值類型 通過NSInvocation設(shè)置參數(shù) 調(diào)用方法
    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);
    }
//返回值為OC對象類型的通過performSelector方式調(diào)用方法
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    return [target performSelector:action withObject:params];
#pragma clang diagnostic pop
}

通過傳入模塊入口類和需要調(diào)用的對應(yīng)方法,我們可以在不引入對應(yīng)模塊頭文件的情況下最終拿到ModuleAViewController的對象,從而實現(xiàn)解耦跳轉(zhuǎn)。

總結(jié)

CTMediator通過runtime的方式解耦,主項目中不需要引入對應(yīng)的模塊頭文件,只需要引入對應(yīng)模塊的CTMediator類擴展。
將一個項目拆分成一個個組件,組件之間相互隔離,專人維護,組件可以單獨提測。這就很好的解決了多人開發(fā)帶來的效率降低問題。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容