CTMediator 原理解析(三)

LuckyRoc發表於2019-01-09

前兩篇文章主要是對這篇文章的內容進行了一個鋪墊,這裡就一起來看下 CTMediator 的實現原理 ,CTMediator是一個單例,主要是基於Mediator模式和Target-Action模式,中間採用了runtime來完成呼叫

CTMediator提供的API分別為:遠端app呼叫入口本地元件呼叫入口釋放某個target快取 這裡主要介紹 本地元件呼叫入口 也是我們最常用的一個方法:

- (id)performTarget:(NSString *)targetName action:(NSString *)actionNameparams:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget;
複製程式碼

實現分析:

    // 從 params   字典中 獲取 swiftModuleName
    NSString *swiftModuleName = params[kCTMediatorParamsKeySwiftTargetModuleName];
    
    // generate target
    NSString *targetClassString = nil;
    if (swiftModuleName.length > 0) {
        targetClassString = [NSString stringWithFormat:@"%@.Target_%@", swiftModuleName, targetName];
    } else {
        targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
    }
    // 根據 targetClassString 從 cachedTarget (快取的Target)獲取 target 
    NSObject *target = self.cachedTarget[targetClassString];
    if (target == nil) {
        // 未獲取到 則通過NSClassFromString將字串轉為應的類
        Class targetClass = NSClassFromString(targetClassString);
        target = [[targetClass alloc] init];
    }

    // generate action
    NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
    SEL action = NSSelectorFromString(actionString);
    
    if (target == nil) {
        // 這裡是處理無響應請求的地方之一,這個demo做得比較簡單,如果沒有可以響應的target,就直接return了。實際開發過程中是可以事先給一個固定的target專門用於在這個時候頂上,然後處理這種請求的
        [self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
        return nil;
    }
    // 是否需要對 Target 進行快取
    if (shouldCacheTarget) {
        // 將 Target 進行快取
        self.cachedTarget[targetClassString] = target;
    }

    // 判斷target物件是否響應action,避免crash
    if ([target respondsToSelector:action]) {
        // 這裡是處理有響應請求的地方
        return [self safePerformAction:action target:target params:params];
    } else {
        // 這裡是處理無響應請求的地方,如果無響應,則嘗試呼叫對應target的notFound方法統一處理
        SEL action = NSSelectorFromString(@"notFound:");
        if ([target respondsToSelector:action]) {
            return [self safePerformAction:action target:target params:params];
        } else {
            // 這裡也是處理無響應請求的地方,在notFound都沒有的時候,這個demo是直接return了。實際開發過程中,可以用前面提到的固定的target頂上的。
            [self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
            // 刪除快取的無用 Target
            [self.cachedTarget removeObjectForKey:targetClassString];
            return nil;
        }
    }
複製程式碼

處理有響應請求的地方會呼叫 - (id)safePerformAction:(SEL)action target:(NSObject *)target params:(NSDictionary *)params 方法

    // NSMethodSignature 記錄著某個方法的返回值型別資訊以及引數型別資訊。用於轉發訊息接收者無法響應的訊息
    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];
        // 執行NSInvocation物件中指定物件的指定方法,並且傳遞指定的引數
        [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;
        // 將返回資料拷貝到提供的快取區(retLoc)內
        [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);
    }
    // 利用RunTime 向target物件傳遞訊息,執行 target 中 action 的方法,傳遞引數 params
    return [target performSelector:action withObject:params];
複製程式碼

總結: CTMediator 根據獲得的target和action資訊,通過objective-C的runtime轉化生成target例項以及對應的action選擇器,然後最終呼叫到目標業務提供的邏輯,完成需求。

相關文章