組件化方案 CTMediator 筆記.

CTMediator 是一個(gè)中間人模式(Mediator Pattern)的實(shí)現(xiàn),用于 iOS 組件化開發(fā)中的模塊間通信方案。

因?yàn)槭欠浅衢T的方案, 這邊就來(lái)看看CTMediator 的具體實(shí)現(xiàn)與使用技巧

1.框架總架構(gòu)

image.png

2.CTMediator 的基本使用方式

2.1. 在每個(gè)模塊中定義一個(gè) Target 類,這個(gè)類包含了模塊中需要提供給其他模塊調(diào)用的所有方法。每個(gè)方法對(duì)應(yīng)一個(gè) Action,方法的參數(shù)和返回值需要定義在一個(gè)字典(NSDictionary)中。

2.2.通過(guò) CTMediator 的 performTarget:action:params:shouldCacheTarget: 方法調(diào)用模塊中的方法。這個(gè)方法需要傳入目標(biāo)模塊(Target)的名稱、要調(diào)用的方法(Action)的名稱、方法參數(shù)以及是否需要緩存目標(biāo)模塊。

2.3. CTMediator 會(huì)在運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建目標(biāo)模塊的實(shí)例,然后調(diào)用指定的方法,并將結(jié)果返回給調(diào)用者。

舉例實(shí)際使用

有一個(gè)用戶模塊,這個(gè)模塊提供了一個(gè)顯示用戶信息的頁(yè)面。
我們可以創(chuàng)建一個(gè) Target,例如叫做 Target_User,
然后在這個(gè) Target 中定義一個(gè) Action,例如叫做 Action_showUserInfo:,
這個(gè) Action 對(duì)應(yīng)一個(gè)方法,用于創(chuàng)建并顯示用戶信息頁(yè)面。方法的參數(shù)可能包含了用戶的 ID,例如 {@"userId" : @"123"}。

3.在其他模塊中,如果你需要顯示用戶信息頁(yè)面,我們可以這樣調(diào)用:
CTMediator *mediator = [CTMediator sharedInstance];
NSDictionary *params = @{@"userId" : @"123"};
UIViewController *userViewController = [mediator performTarget:@"User" action:@"showUserInfo" params:params shouldCacheTarget:NO];

// 然后可以將 userViewController 推入到導(dǎo)航控制器中
[self.navigationController pushViewController:userViewController animated:YES];

[mediator performTarget:@"User" action:@"showUserInfo" params:params shouldCacheTarget:NO]; 是使用 CTMediator 執(zhí)行一個(gè)操作。這個(gè)操作可能返回一個(gè)對(duì)象,這里是一個(gè) UIViewController 實(shí)例,也可能返回其他類型的對(duì)象,取決于具體的實(shí)現(xiàn)。下面是各個(gè)參數(shù)的作用:

"User":這是 target 的名稱,對(duì)應(yīng)的是 Target_User 類。這個(gè)類應(yīng)該在用戶模塊中定義,并包含了需要提供給其他模塊調(diào)用的所有方法。每個(gè)方法對(duì)應(yīng)一個(gè) action。

"showUserInfo":這是 action 的名稱,對(duì)應(yīng)的是 Target_User 類中的 Action_showUserInfo: 方法。這個(gè)方法被設(shè)計(jì)用來(lái)創(chuàng)建并返回一個(gè)顯示用戶信息的 UIViewController 實(shí)例。

params:這是傳遞給 action 的參數(shù)。參數(shù)需要封裝在一個(gè)字典中,例如 @{@"userId" : @"123"}。在這個(gè)例子中,字典包含了一個(gè)鍵為 "userId" 的項(xiàng),值為 "123"。這個(gè)值將被 Action_showUserInfo: 方法用來(lái)獲取用戶的信息。

NO:這個(gè)參數(shù)決定是否應(yīng)該緩存 target。如果這個(gè)值為 YES,那么 CTMediator 將會(huì)在第一次創(chuàng)建 Target_User 實(shí)例后,將這個(gè)實(shí)例緩存起來(lái)。以后再需要執(zhí)行 Target_Useraction 時(shí),將會(huì)使用這個(gè)緩存的實(shí)例,而不是再次創(chuàng)建新的實(shí)例。如果這個(gè)值為 NO,那么 CTMediator 每次都會(huì)創(chuàng)建新的 Target_User 實(shí)例。通常來(lái)說(shuō),如果 target 的創(chuàng)建和銷毀開銷很大,或者 target 需要保存一些狀態(tài)信息,那么可以考慮使用緩存。否則,為了避免占用過(guò)多的內(nèi)存,不應(yīng)該使用緩存。

調(diào)用交互邏輯圖示.png

4.CTMediator涉及的 OC runtime 技術(shù)

主要在動(dòng)態(tài)獲取 target 類, 動(dòng)態(tài)創(chuàng)建 target 實(shí)例,以及動(dòng)態(tài)獲取 action 方法.

- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
{
    //... 省略部分代碼

    // 生成 target 類名
    NSString *targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
    // 動(dòng)態(tài)獲取 target 類
    Class targetClass = NSClassFromString(targetClassString);
    // 動(dòng)態(tài)創(chuàng)建 target 實(shí)例
    NSObject *target = [[targetClass alloc] init];

    // 生成 action 方法名
    NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
    // 動(dòng)態(tài)獲取 action 方法
    SEL action = NSSelectorFromString(actionString);

    // 動(dòng)態(tài)調(diào)用 action 方法
    if ([target respondsToSelector:action]) {
4.1動(dòng)態(tài)獲取 target 類

NSClassFromString 是一個(gè) Objective-C 的運(yùn)行時(shí)函數(shù),它可以根據(jù)提供的類名字符串動(dòng)態(tài)地獲取類的 Class 對(duì)象。它的參數(shù)是一個(gè)類名的字符串,返回值是一個(gè) Class 類型的對(duì)象。如果找不到對(duì)應(yīng)的類,它會(huì)返回 nil。

Class targetClass = NSClassFromString(targetClassString);

targetClassString 是一個(gè)包含了完整類名的字符串。NSClassFromString 會(huì)在運(yùn)行時(shí)查找有沒(méi)有這個(gè)名稱的類。如果找到了,它就返回這個(gè)類的 Class 對(duì)象;如果找不到,它就返回 nil

動(dòng)態(tài)獲取類的能力是 Objective-C 的動(dòng)態(tài)特性的一部分,它讓 Objective-C 的行為可以在運(yùn)行時(shí)改變。這也是 CTMediator 能夠?qū)崿F(xiàn)模塊間解耦通信的關(guān)鍵所在:CTMediator可以在運(yùn)行時(shí)根據(jù)需要?jiǎng)討B(tài)地找到并調(diào)用任何模塊的任何方法,而無(wú)需在編譯時(shí)就確定這些信息。

注意: 由于 NSClassFromString 是基于字符串的,所以在使用它時(shí)需要小心確保類名的字符串是正確的。如果字符串有誤,NSClassFromString 可能會(huì)返回 nil,導(dǎo)致后續(xù)的操作失敗。

4.2 動(dòng)態(tài)獲取 action 方法
    SEL action = NSSelectorFromString(actionString);

NSSelectorFromString 是 Objective-C 的一個(gè)運(yùn)行時(shí)函數(shù),它可以根據(jù)提供的方法名字符串(即選擇器名)動(dòng)態(tài)地獲取一個(gè) SEL 類型的選擇器。選擇器(selector)在 Objective-C 中是一種可以表示和調(diào)用方法的數(shù)據(jù)類型。
通過(guò)NSSelectorFromString 這樣一個(gè)動(dòng)態(tài)特性,CTMediator 只需要知道方法名的字符串,就可以調(diào)用任何模塊的任何方法,而無(wú)需在編譯時(shí)就知道這些信息。

在這行代碼中,actionString 是一個(gè)包含了完整方法名的字符串。
NSSelectorFromString 會(huì)在運(yùn)行時(shí)查找有沒(méi)有這個(gè)名稱的方法。如果找到了,它就返回這個(gè)方法的選擇器;如果找不到,它就返回 NULL。

4.2.2 衍生知識(shí)點(diǎn) SEL 是什么?

我們先介紹一下 OC 語(yǔ)言發(fā)送消息的機(jī)制.

當(dāng)在 OC 中向一個(gè)對(duì)象發(fā)送一個(gè)消息時(shí),運(yùn)行時(shí)系統(tǒng)會(huì)通過(guò)對(duì)象的 isa 指針找到類對(duì)象,然后在類對(duì)象方法列表中查找與消息對(duì)應(yīng)的 SEL。如果找到了,就會(huì)獲取對(duì)應(yīng)的IMP,然后調(diào)用這個(gè)函數(shù)指針指向的代碼,執(zhí)行方法的實(shí)現(xiàn)。如果在類對(duì)象方法列表中找不到,就會(huì)在其元類方法列表中繼續(xù)查找,直到找到為止。如果在所有的超類中都找不到,就會(huì)觸發(fā)消息轉(zhuǎn)發(fā)(message forwarding)機(jī)制。
這個(gè)部分可以這樣圖示.

           NSObject               NSObject's class              NSObject's meta-class
+--------------+               +--------------+               +--------------+
| isa          | -------------> | isa          | -------------> | isa          |
|              |               | superclass   |               | superclass   |
|              |               | cache        |               | cache        |
|              |               | vtable       |               | vtable       |
|              |               | sarray       |               | sarray       |
|              |               | class_ro     |               | class_ro     |
|              |               | class_rw     |               | class_rw     |
+--------------+               | method_list  |               | method_list  |
                               |              |               |              |
                               | SEL  |  IMP  |               | SEL  |  IMP  |
                               |------+-------|               |------+-------|
                               | foo  |  *foo |               | foo  |  *foo |
                               | bar  |  *bar |               | bar  |  *bar |
                               +--------------+               +--------------+

SEL:這是"selector" 的簡(jiǎn)寫,它是一個(gè)表示方法名的類型。在 Objective-C 中,當(dāng)你發(fā)送一個(gè)消息給一個(gè)對(duì)象時(shí),你其實(shí)是在告訴這個(gè)對(duì)象執(zhí)行一個(gè)selector。你可以把selector看作是方法名。每個(gè) selector在 Objective-C 的運(yùn)行時(shí)系統(tǒng)中都有一個(gè)唯一的地址,即使在不同的類中定義的完全相同的方法名,它們的 selector 地址也是相同的

IMP:這是"implementation"(執(zhí)行)的簡(jiǎn)寫,它是一個(gè)函數(shù)指針,指向方法的實(shí)現(xiàn)。在 Objective-C 中,每個(gè)類的實(shí)例都有一個(gè)類對(duì)象,類對(duì)象中存儲(chǔ)了類的方法列表。每個(gè)方法列表元素是一個(gè)結(jié)構(gòu)體,其中包含一個(gè) SEL 和一個(gè) IMP。

SEL 是方法名
IMP 是方法的實(shí)現(xiàn)。

4.2.3舉例說(shuō)明

例如,你有一個(gè) Person 對(duì)象,你向這個(gè)對(duì)象發(fā)送一個(gè) sayHello 消息:
Person *person = [[Person alloc] init];
[person sayHello];

+-----------------+     message      +-----------------+      SEL       +-----------------+      IMP       +-----------------+
|  Person Object  | ----------------> |  Person Class  | -------------> |  Method List    | -------------> |  sayHello Code  |
|                 |                  |                |                |                 |                |                 |
|  receive        |                  |  search for    |                |  "sayHello"     |                |  printf("Hello |
|  "sayHello"     |                  |  "sayHello"    |                |  ----> IMP      |                |  World!\n");   |
|  message        |                  +-----------------+                +-----------------+                +-----------------+
        |                             |  (if not found)
        |                             |  Search in superclass
        v                             v
+-----------------+           +-----------------+
|  Start          |           |  Message        |
|  [person         |           |  Forwarding     |
|  sayHello];     |           |                 |
+-----------------+           +-----------------+

4.2.4流程解釋

1.Person 對(duì)象收到 sayHello 消息。
2.運(yùn)行時(shí)系統(tǒng)通過(guò) Person 對(duì)象的 isa 指針找到 Person 類。
3.在 Person 類的方法列表中查找 sayHello 的選擇器(SEL)。
4.如果找到了,就獲取 sayHello 方法的實(shí)現(xiàn)(IMP)并執(zhí)行這個(gè)實(shí)現(xiàn)。
5.如果沒(méi)有找到,就在 Person 類的父類的方法列表中查找。

  1. 如果在所有的父類中都找不到,就會(huì)觸發(fā)消息轉(zhuǎn)發(fā)(Message Forwarding)機(jī)制。
4.2.5舉例升級(jí), 如何使用CTMediator 做這個(gè)消息發(fā)送.
// 創(chuàng)建一個(gè) CTMediator 實(shí)例
CTMediator *mediator = [CTMediator sharedInstance];

// 創(chuàng)建一個(gè)字典來(lái)存儲(chǔ)需要傳遞給 "sayHello" 方法的參數(shù)
NSDictionary *params = @{};

// 使用 CTMediator 的 performTarget:action:params:shouldCacheTarget: 方法發(fā)送 "sayHello" 消息
[mediator performTarget:@"Person" action:@"sayHello" params:params shouldCacheTarget:NO];

CTMediator 實(shí)例的 performTarget:action:params:shouldCacheTarget: 方法被用來(lái)發(fā)送 sayHello消息。這個(gè)方法的參數(shù)如下:

target:這是你想要發(fā)送消息的目標(biāo)對(duì)象的名稱。在這個(gè)例子中,目標(biāo)對(duì)象是 Person。
action:這是你想要執(zhí)行的方法的名稱。在這個(gè)例子中,你想要執(zhí)行的方法是 sayHello。
params:這是一個(gè)字典,包含你想要傳遞給目標(biāo)方法的參數(shù)。在這個(gè)例子中,sayHello 方法不需要任何參數(shù),所以這個(gè)字典是空的。
shouldCacheTarget:這是一個(gè)布爾值,決定是否緩存目標(biāo)對(duì)象。如果設(shè)置為 YES,CTMediator 會(huì)緩存目標(biāo)對(duì)象,以便下次可以快速找到它。如果設(shè)置為 NO,CTMediator 不會(huì)緩存目標(biāo)對(duì)象。

當(dāng)我們調(diào)用 performTarget:action:params:shouldCacheTarget: 方法時(shí),CTMediator 會(huì)在運(yùn)行時(shí)查找并調(diào)用 Person 對(duì)象的 sayHello 方法。這就是 CTMediator 的工作原理:它可以在運(yùn)行時(shí)動(dòng)態(tài)地找到并調(diào)用任何對(duì)象的任何方法,而無(wú)需在編譯時(shí)就確定這些信息。

下面是CTMediator去調(diào)用這個(gè)方法的流程圖.

+-----------------+        message        +-----------------+       SEL       +-----------------+       IMP       +-----------------+
|  CTMediator     | ------------------->  |  Person Class   | ------------->  |  Method List    | ------------->  |  sayHello Code  |
|                 |                       |                 |                 |                 |                 |                 |
|  performTarget: |                       |  search for     |                 |  "sayHello"     |                 |  printf("Hello |
|  "Person"       |                       |  "sayHello"     |                 |  ----> IMP      |                 |  World!\n");   |
|  action:        |                       +-----------------+                 +-----------------+                 +-----------------+
|  "sayHello"     |                       |  (if not found)
|  params:        |                       |  Search in superclass
|  {}             |                       v
|  shouldCache    |               +-----------------+
|  Target: NO     |               |  Message        |
+-----------------+               |  Forwarding     |
                                  |                 |
                                  +-----------------+

這樣就可以在不知道Person class 的情況下,直接調(diào)用Person 的方法.

5 參數(shù)透?jìng)? 且支持 block 回調(diào)

使用 CTMediator 的過(guò)程中,方法block回調(diào)可以被定義為一個(gè) block,然后 將這個(gè) block作為參數(shù)傳遞給目標(biāo)方法。在目標(biāo)方法完成后,這個(gè)回調(diào) block 將被執(zhí)行,你可以在回調(diào) block 中接收和處理目標(biāo)方法的執(zhí)行結(jié)果。

// 創(chuàng)建 Calculator 對(duì)象
Calculator *calculator = [[Calculator alloc] init];

// 調(diào)用 `calculate:withCompletion:` 方法
[calculator calculate:@{@"input1": @10, @"input2": @20} withCompletion:^(NSInteger result) {
    NSLog(@"The result is %ld", (long)result);
}];


// Calculator 類的實(shí)現(xiàn)
@implementation Calculator

- (void)calculate:(NSDictionary *)params withCompletion:(void (^)(NSInteger))completion {
    NSNumber *input1 = params[@"input1"];
    NSNumber *input2 = params[@"input2"];
    NSInteger result = input1.integerValue + input2.integerValue;
    
    if (completion) {
        completion(result);
    }
}

@end


然后,我們可以使用 CTMediator 來(lái)調(diào)用 calculate:withCompletion: 方法并獲取計(jì)算結(jié)果,代碼如下:

// 創(chuàng)建一個(gè) CTMediator 實(shí)例
CTMediator *mediator = [CTMediator sharedInstance];

// 創(chuàng)建一個(gè)字典來(lái)存儲(chǔ)需要傳遞給 `calculate:withCompletion:` 方法的參數(shù)
NSDictionary *params = @{
    @"input1": @10,
    @"input2": @20,
    @"callback": ^(NSInteger result) {
        NSLog(@"The result is %ld", (long)result);
    }
};

// 使用 CTMediator 的 `performTarget:action:params:shouldCacheTarget:` 方法發(fā)送消息
[mediator performTarget:@"Calculator" action:@"calculate:withCompletion:" params:params shouldCacheTarget:NO];

創(chuàng)建了一個(gè)包含三個(gè)鍵值對(duì)的 params 字典:

"input1" 的值是 @10。
"input2" 的值是 @20。
"callback" 的值是一個(gè) block,這個(gè) block 會(huì)在 calculate:withCompletion:方法完成后被調(diào)用,并接收計(jì)算結(jié)果作為參數(shù)。
當(dāng) performTarget:action:params:shouldCacheTarget:方法被調(diào)用時(shí),CTMediator 會(huì)動(dòng)態(tài)地找到名為 Calculator 的類,然后在這個(gè)類中查找 calculate:withCompletion:方法。如果找到了這個(gè)方法,CTMediator 就會(huì)創(chuàng)建一個(gè) Calculator 實(shí)例,然后調(diào)用這個(gè)實(shí)例的calculate:withCompletion:方法,并把 params字典作為參數(shù)傳遞給這個(gè)方法。

在 calculate:withCompletion: 方法中,你可以從 params 字典中取出你需要的參數(shù),例如:

- (void)calculate:(NSDictionary *)params withCompletion:(void (^)(NSInteger))completion {
    NSNumber *input1 = params[@"input1"];
    NSNumber *input2 = params[@"input2"];
    void (^callback)(NSInteger) = params[@"callback"];
    
    // 進(jìn)行計(jì)算
    NSInteger result = input1.integerValue + input2.integerValue;
    
    // 調(diào)用回調(diào) block
    if (callback) {
        callback(result);
    }
}

通過(guò)這個(gè)字典可以將 block 也一起傳入.

6. 緩存機(jī)制

緩存目標(biāo)類實(shí)例,避免重復(fù)初始化,優(yōu)化性能。

當(dāng)我們通過(guò) CTMediator 請(qǐng)求一個(gè)目標(biāo)并執(zhí)行一個(gè)動(dòng)作時(shí),CTMediator 會(huì)首先查看是否已經(jīng)創(chuàng)建并緩存了這個(gè)目標(biāo)的實(shí)例。如果已經(jīng)創(chuàng)建了,那么 CTMediator 就直接使用這個(gè)已經(jīng)創(chuàng)建的實(shí)例;如果還沒(méi)有創(chuàng)建,那么 CTMediator 就會(huì)創(chuàng)建一個(gè)新的實(shí)例,然后把這個(gè)新創(chuàng)建的實(shí)例緩存起來(lái),以供后續(xù)使用。

這種目標(biāo)緩存的機(jī)制可以幫助避免重復(fù)初始化目標(biāo)實(shí)例,從而提高程序的性能。

- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
{
    // 獲取 target 類名字符串
    NSString *targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];

    // 嘗試從緩存中獲取 target 實(shí)例
    NSObject *target = [self safeFetchCachedTarget:targetClassString];
    
    if (target == nil) {
        // 如果緩存中沒(méi)有找到 target 實(shí)例,則創(chuàng)建一個(gè)新的實(shí)例
        Class targetClass = NSClassFromString(targetClassString);
        target = [[targetClass alloc] init];
    }
    
    if (shouldCacheTarget) {
        // 如果 shouldCacheTarget 參數(shù)為 YES,則將新創(chuàng)建的 target 實(shí)例緩存起來(lái)
        [self safeSetCachedTarget:target key:targetClassString];
    }
    
}

safeFetchCachedTarget: 和 safeSetCachedTarget:key: 這兩個(gè)方法在 CTMediator 中用于獲取和設(shè)置緩存的目標(biāo)實(shí)例。

- (NSObject *)safeFetchCachedTarget:(NSString *)key {
    @synchronized (self) {
        return self.cachedTarget[key];
    }
}

- (void)safeSetCachedTarget:(NSObject *)target key:(NSString *)key {
    @synchronized (self) {
        self.cachedTarget[key] = target;
    }
}

safeFetchCachedTarget: 方法通過(guò)給定的鍵從緩存中獲取對(duì)應(yīng)的目標(biāo)實(shí)例。它是線程安全的,因?yàn)樗褂昧?@synchronized 關(guān)鍵字來(lái)確保在多線程環(huán)境下的安全訪問(wèn):

self.cachedTarget 這個(gè)屬性,它是一個(gè) NSMutableDictionary 類型的字典,用于存儲(chǔ)緩存的目標(biāo)實(shí)例。這個(gè)字典的鍵是目標(biāo)類名的字符串形式,而值是對(duì)應(yīng)的目標(biāo)實(shí)例。

7.異常處理

當(dāng)我們嘗試調(diào)用一個(gè)目標(biāo)的某個(gè)動(dòng)作時(shí),CTMediator會(huì)首先檢查這個(gè)目標(biāo)是否存在,然后檢查這個(gè)目標(biāo)是否響應(yīng)這個(gè)動(dòng)作。如果目標(biāo)不存在或者不響應(yīng)這個(gè)動(dòng)作,CTMediator 就會(huì)調(diào)用 NoTargetActionResponseWithTargetString:selectorString:originParams:方法來(lái)處理這個(gè)異常。在這個(gè)方法中,你可以根據(jù)你的需要來(lái)定義如何處理這種異常,例如,可以輸出一個(gè)錯(cuò)誤提示,或者調(diào)用一個(gè)備用的目標(biāo)或動(dòng)作。

- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
{
    // ... 其他代碼 ...
    
    if (target == nil) {
        // 如果目標(biāo)不存在,則調(diào)用 `NoTargetActionResponseWithTargetString:selectorString:originParams:` 方法來(lái)處理異常
        [self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
        return nil;
    }
    
    if ([target respondsToSelector:action]) {
        // 如果目標(biāo)響應(yīng)這個(gè)動(dòng)作,則正常執(zhí)行這個(gè)動(dòng)作
        return [self safePerformAction:action target:target params:params];
    } else {
        // 如果目標(biāo)不響應(yīng)這個(gè)動(dòng)作,則調(diào)用 `NoTargetActionResponseWithTargetString:selectorString:originParams:` 方法來(lái)處理異常
        [self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
        return nil;
    }
}
8.容易擴(kuò)展

CTMediator 作為中介者模式的實(shí)現(xiàn),其核心職責(zé)是負(fù)責(zé)組件之間的通信。但是因?yàn)樵O(shè)計(jì)比較輕便,比較靈活,CTMediator 也可以被擴(kuò)展來(lái)實(shí)現(xiàn)各種輔助方法,從而增強(qiáng)其功能。

核心方法 - (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget

- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
{
   // 檢查參數(shù)的有效性
    if (![self isValidParams:params]) {
        NSLog(@"Invalid parameters: %@", params);
        return nil;
    }
    
 // 檢查執(zhí)行結(jié)果
    id result = [self safePerformAction:action target:target params:params];
    if (result == nil) {
        // 如果執(zhí)行失敗,則進(jìn)行錯(cuò)誤處理
        [self handleErrorWithTargetName:targetName actionName:actionName params:params];
    }
}

// 檢查參數(shù)的有效性
- (BOOL)isValidParams:(NSDictionary *)params {
    // 在這里,我們簡(jiǎn)單地假設(shè)所有的參數(shù)都必須是 NSString 類型
    for (id value in params.allValues) {
        if (![value isKindOfClass:[NSString class]]) {
            return NO;
        }
    }
    
    return YES;
}

// 處理錯(cuò)誤
- (void)handleErrorWithTargetName:(NSString *)targetName actionName:(NSString *)actionName params:(NSDictionary *)params {
    NSLog(@"Failed to perform action %@ on target %@ with parameters: %@", actionName, targetName, params);
}

最后. 是CTMediator 的源碼注釋, 認(rèn)真看看.

// 引入 CTMediator 頭文件
#import "CTMediator.h"
// 引入 Objective-C 運(yùn)行時(shí)頭文件,用于動(dòng)態(tài)調(diào)用方法等
#import <objc/runtime.h>
// 引入 CoreGraphics 頭文件,用于處理圖形相關(guān)的操作
#import <CoreGraphics/CoreGraphics.h>

// 定義一個(gè)常量字符串,用于獲取 Swift 模塊名稱
NSString * const kCTMediatorParamsKeySwiftTargetModuleName = @"kCTMediatorParamsKeySwiftTargetModuleName";

// CTMediator 的接口聲明
@interface CTMediator ()

// 聲明一個(gè)屬性,用于存儲(chǔ)已緩存的 target
@property (nonatomic, strong) NSMutableDictionary *cachedTarget;

@end

// CTMediator 的實(shí)現(xiàn)
@implementation CTMediator

// 公共方法

// 獲取 CTMediator 的單例
+ (instancetype)sharedInstance
{
    // 聲明一個(gè)靜態(tài)變量用于存儲(chǔ)單例對(duì)象
    static CTMediator *mediator;
    // 聲明一個(gè) dispatch_once_t 變量,用于保證單例創(chuàng)建的線程安全
    static dispatch_once_t onceToken;
    // 使用 GCD 的 dispatch_once 函數(shù)創(chuàng)建單例
    dispatch_once(&onceToken, ^{
        mediator = [[CTMediator alloc] init];
        // 初始化 cachedTarget,避免多線程重復(fù)初始化
        [mediator cachedTarget];
    });
    // 返回單例對(duì)象
    return mediator;
}

// 通過(guò) URL 執(zhí)行 action,并將結(jié)果通過(guò) completion 回調(diào)返回
- (id)performActionWithUrl:(NSURL *)url completion:(void (^)(NSDictionary *))completion
{
    // 檢查 url 是否為空或者不是 NSURL 類型
    if (url == nil || ![url isKindOfClass:[NSURL class]]) {
        return nil;
    }
    
    // 創(chuàng)建一個(gè) NSMutableDictionary 用于存儲(chǔ)參數(shù)
    NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
    // 使用 NSURLComponents 解析 url
    NSURLComponents *urlComponents = [[NSURLComponents alloc] initWithString:url.absoluteString];
    // 遍歷所有的參數(shù)并存入 params
    [urlComponents.queryItems enumerateObjectsUsingBlock:^(NSURLQueryItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if (obj.value && obj.name) {
            [params setObject:obj.value forKey:obj.name];
        }
    }];
    
    // 從 url 的 path 中獲取 action 名稱,并將前面的 "/" 刪除
    NSString *actionName = [url.path stringByReplacingOccurrencesOfString:@"/" withString:@""];
    // 如果 actionName 以 "native" 開頭,返回 NO
    if ([actionName hasPrefix:@"native"]) {
        return @(NO);
    }
    
    // 執(zhí)行 target-action,并將結(jié)果返回
    id result = [self performTarget:url.host action:actionName params:params shouldCacheTarget:NO];
    // 如果有 completion 回調(diào),執(zhí)行回調(diào)
    if (completion) {
        if (result) {
            completion(@{@"result":result});
        } else {
            completion(nil);
        }
    }
    // 返回結(jié)果
    return result;
}

// 執(zhí)行 target-action,并將結(jié)果返回
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
{
    // 檢查 targetName 和 actionName 是否為空
    if (targetName == nil || actionName == nil) {
        return nil;
    }
    
    // 從 params 中獲取 Swift 模塊名
    NSString *swiftModuleName = params[kCTMediatorParamsKeySwiftTargetModuleName];
    
    // 生成 target
    NSString *targetClassString = nil;
    // 如果有 Swift 模塊名,那么 targetClassString 為 "模塊名.Target_目標(biāo)名"
    if (swiftModuleName.length > 0) {
        targetClassString = [NSString stringWithFormat:@"%@.Target_%@", swiftModuleName, targetName];
    } else {
        // 否則 targetClassString 為 "Target_目標(biāo)名"
        targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
    }
    // 從緩存中獲取 target
    NSObject *target = [self safeFetchCachedTarget:targetClassString];
    // 如果緩存中沒(méi)有 target,創(chuàng)建并緩存 target
    if (target == nil) {
        Class targetClass = NSClassFromString(targetClassString);
        target = [[targetClass alloc] init];
    }

    // 生成 action
    NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
    // 通過(guò) actionString 創(chuàng)建一個(gè) SEL
    SEL action = NSSelectorFromString(actionString);
    
    // 如果 target 不存在,調(diào)用 NoTargetActionResponseWithTargetString:selectorString:originParams: 方法處理
    if (target == nil) {
        [self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
        return nil;
    }
    
    // 如果需要緩存 target,將 target 緩存起來(lái)
    if (shouldCacheTarget) {
        [self safeSetCachedTarget:target key:targetClassString];
    }

    // 如果 target 能響應(yīng) action,執(zhí)行 action 并返回結(jié)果
    if ([target respondsToSelector:action]) {
        return [self safePerformAction:action target:target params:params];
    } else {
        // 如果 target 不能響應(yīng) action,嘗試調(diào)用 notFound: 方法
        SEL action = NSSelectorFromString(@"notFound:");
        if ([target respondsToSelector:action]) {
            return [self safePerformAction:action target:target params:params];
        } else {
            // 如果還是無(wú)法響應(yīng),調(diào)用 NoTargetActionResponseWithTargetString:selectorString:originParams: 方法處理,并移除緩存的 target
            [self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
            @synchronized (self) {
                [self.cachedTarget removeObjectForKey:targetClassString];
            }
            return nil;
        }
    }
}

// 釋放緩存的 target
- (void)releaseCachedTargetWithFullTargetName:(NSString *)fullTargetName
{
    // 如果 fullTargetName 為空,直接返回
    if (fullTargetName == nil) {
        return;
    }
    // 移除緩存的 target
    @synchronized (self) {
        [self.cachedTarget removeObjectForKey:fullTargetName];
    }
}

// 檢查指定的 target 和 module 是否存在
- (BOOL)check:(NSString * _Nullable)targetName moduleName:(NSString * _Nullable)moduleName{
    // 如果有 module 名,返回 "模塊名.Target_目標(biāo)名" 對(duì)應(yīng)的 Class 是否存在
    if (moduleName.length > 0) {
        return NSClassFromString([NSString stringWithFormat:@"%@.Target_%@", moduleName, targetName]) != nil;
    } else {
        // 否則返回 "Target_目標(biāo)名" 對(duì)應(yīng)的 Class 是否存在
        return NSClassFromString([NSString stringWithFormat:@"Target_%@", targetName]) != nil;
    }
}

// 私有方法

// 處理無(wú)法響應(yīng) action 的情況
- (void)NoTargetActionResponseWithTargetString:(NSString *)targetString selectorString:(NSString *)selectorString originParams:(NSDictionary *)originParams
{
    // 創(chuàng)建一個(gè) "Action_response:" 的 SEL
    SEL action = NSSelectorFromString(@"Action_response:");
    // 創(chuàng)建一個(gè) "Target_NoTargetAction" 的 target
    NSObject *target = [[NSClassFromString(@"Target_NoTargetAction") alloc] init];
    
    // 創(chuàng)建一個(gè) params 字典,包含原始的參數(shù)、target 名和 selector 名
    NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
    params[@"originParams"] = originParams;
    params[@"targetString"] = targetString;
    params[@"selectorString"] = selectorString;
    
    // 執(zhí)行 action 并傳入 params
    [self safePerformAction:action target:target params:params];
}

// 安全執(zhí)行 action
- (id)safePerformAction:(SEL)action target:(NSObject *)target params:(NSDictionary *)params
{
    // 獲取 action 的方法簽名
    NSMethodSignature* methodSig = [target methodSignatureForSelector:action];
    // 如果方法簽名不存在,返回 nil
    if(methodSig == nil) {
        return nil;
    }
    // 獲取返回類型
    const char* retType = [methodSig methodReturnType];

    // 根據(jù)返回類型,創(chuàng)建一個(gè) NSInvocation,并設(shè)置參數(shù)、selector 和 target
    // 如果返回類型是 void,執(zhí)行 action 并返回 nil
    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;
    }

    // 如果返回類型是 NSInteger、BOOL、CGFloat 或 NSUInteger,執(zhí)行 action 并返回結(jié)果
    // 如果返回類型是 id,執(zhí)行 action 并返回結(jié)果
    // 注意,這里省略了具體的代碼,需要根據(jù)實(shí)際的返回類型寫出相應(yīng)的代碼

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    // 如果返回類型不是上面的任何一種,直接執(zhí)行 action 并返回結(jié)果
    return [target performSelector:action withObject:params];
#pragma clang diagnostic pop
}

// 獲取和設(shè)置方法

// 獲取 cachedTarget
- (NSMutableDictionary *)cachedTarget
{
    // 如果 cachedTarget 不存在,創(chuàng)建 cachedTarget
    if (_cachedTarget == nil) {
        _cachedTarget = [[NSMutableDictionary alloc] init];
    }
    // 返回 cachedTarget
    return _cachedTarget;
}

// 從緩存中獲取 target
- (NSObject *)safeFetchCachedTarget:(NSString *)key {
    // 使用 @synchronized 來(lái)保證線程安全
    @synchronized (self) {
        // 從 cachedTarget 中獲取指定的 target
        return self.cachedTarget[key];
    }
}

// 將 target 緩存起來(lái)
- (void)safeSetCachedTarget:(NSObject *)target key:(NSString *)key {
    // 使用 @synchronized 來(lái)保證線程安全
    @synchronized (self) {
        // 將 target 緩存到 cachedTarget 中
        self.cachedTarget[key] = target;
    }
}

@end

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

相關(guān)閱讀更多精彩內(nèi)容

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