组件化方案 CTMediator 笔记.

CTMediator 是一个中间人模式(Mediator Pattern)的实现,用于 iOS 组件化开发中的模块间通信方案。

因为是非常热门的方案, 这边就来看看CTMediator 的具体实现与使用技巧

1.框架总架构

image.png

2.CTMediator 的基本使用方式

2.1. 在每个模块中定义一个 Target 类,这个类包含了模块中需要提供给其他模块调用的所有方法。每个方法对应一个 Action,方法的参数和返回值需要定义在一个字典(NSDictionary)中。

2.2.通过 CTMediator 的 performTarget:action:params:shouldCacheTarget: 方法调用模块中的方法。这个方法需要传入目标模块(Target)的名称、要调用的方法(Action)的名称、方法参数以及是否需要缓存目标模块。

2.3. CTMediator 会在运行时动态创建目标模块的实例,然后调用指定的方法,并将结果返回给调用者。

举例实际使用

有一个用户模块,这个模块提供了一个显示用户信息的页面。
我们可以创建一个 Target,例如叫做 Target_User
然后在这个 Target 中定义一个 Action,例如叫做 Action_showUserInfo:
这个 Action 对应一个方法,用于创建并显示用户信息页面。方法的参数可能包含了用户的 ID,例如 {@"userId" : @"123"}

3.在其他模块中,如果你需要显示用户信息页面,我们可以这样调用:
CTMediator *mediator = [CTMediator sharedInstance];
NSDictionary *params = @{@"userId" : @"123"};
UIViewController *userViewController = [mediator performTarget:@"User" action:@"showUserInfo" params:params shouldCacheTarget:NO];

// 然后可以将 userViewController 推入到导航控制器中
[self.navigationController pushViewController:userViewController animated:YES];

[mediator performTarget:@"User" action:@"showUserInfo" params:params shouldCacheTarget:NO]; 是使用 CTMediator 执行一个操作。这个操作可能返回一个对象,这里是一个 UIViewController 实例,也可能返回其他类型的对象,取决于具体的实现。下面是各个参数的作用:

"User":这是 target 的名称,对应的是 Target_User 类。这个类应该在用户模块中定义,并包含了需要提供给其他模块调用的所有方法。每个方法对应一个 action。

"showUserInfo":这是 action 的名称,对应的是 Target_User 类中的 Action_showUserInfo: 方法。这个方法被设计用来创建并返回一个显示用户信息的 UIViewController 实例。

params:这是传递给 action 的参数。参数需要封装在一个字典中,例如 @{@"userId" : @"123"}。在这个例子中,字典包含了一个键为 "userId" 的项,值为 "123"。这个值将被 Action_showUserInfo: 方法用来获取用户的信息。

NO:这个参数决定是否应该缓存 target。如果这个值为 YES,那么 CTMediator 将会在第一次创建 Target_User 实例后,将这个实例缓存起来。以后再需要执行 Target_Useraction 时,将会使用这个缓存的实例,而不是再次创建新的实例。如果这个值为 NO,那么 CTMediator 每次都会创建新的 Target_User 实例。通常来说,如果 target 的创建和销毁开销很大,或者 target 需要保存一些状态信息,那么可以考虑使用缓存。否则,为了避免占用过多的内存,不应该使用缓存。

调用交互逻辑图示.png

4.CTMediator涉及的 OC runtime 技术

主要在动态获取 target 类, 动态创建 target 实例,以及动态获取 action 方法.

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

    // 生成 target 类名
    NSString *targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
    // 动态获取 target 类
    Class targetClass = NSClassFromString(targetClassString);
    // 动态创建 target 实例
    NSObject *target = [[targetClass alloc] init];

    // 生成 action 方法名
    NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
    // 动态获取 action 方法
    SEL action = NSSelectorFromString(actionString);

    // 动态调用 action 方法
    if ([target respondsToSelector:action]) {
4.1动态获取 target 类

NSClassFromString 是一个 Objective-C 的运行时函数,它可以根据提供的类名字符串动态地获取类的 Class 对象。它的参数是一个类名的字符串,返回值是一个 Class 类型的对象。如果找不到对应的类,它会返回 nil。

Class targetClass = NSClassFromString(targetClassString);

targetClassString 是一个包含了完整类名的字符串。NSClassFromString 会在运行时查找有没有这个名称的类。如果找到了,它就返回这个类的 Class 对象;如果找不到,它就返回 nil

动态获取类的能力是 Objective-C 的动态特性的一部分,它让 Objective-C 的行为可以在运行时改变。这也是 CTMediator 能够实现模块间解耦通信的关键所在:CTMediator可以在运行时根据需要动态地找到并调用任何模块的任何方法,而无需在编译时就确定这些信息。

注意: 由于 NSClassFromString 是基于字符串的,所以在使用它时需要小心确保类名的字符串是正确的。如果字符串有误,NSClassFromString 可能会返回 nil,导致后续的操作失败。

4.2 动态获取 action 方法
    SEL action = NSSelectorFromString(actionString);

NSSelectorFromString 是 Objective-C 的一个运行时函数,它可以根据提供的方法名字符串(即选择器名)动态地获取一个 SEL 类型的选择器。选择器(selector)在 Objective-C 中是一种可以表示和调用方法的数据类型。
通过NSSelectorFromString 这样一个动态特性,CTMediator 只需要知道方法名的字符串,就可以调用任何模块的任何方法,而无需在编译时就知道这些信息。

在这行代码中,actionString 是一个包含了完整方法名的字符串。
NSSelectorFromString 会在运行时查找有没有这个名称的方法。如果找到了,它就返回这个方法的选择器;如果找不到,它就返回 NULL

4.2.2 衍生知识点 SEL 是什么?

我们先介绍一下 OC 语言发送消息的机制.

当在 OC 中向一个对象发送一个消息时,运行时系统会通过对象的 isa 指针找到类对象,然后在类对象方法列表中查找与消息对应的 SEL。如果找到了,就会获取对应的IMP,然后调用这个函数指针指向的代码,执行方法的实现。如果在类对象方法列表中找不到,就会在其元类方法列表中继续查找,直到找到为止。如果在所有的超类中都找不到,就会触发消息转发(message forwarding)机制。
这个部分可以这样图示.

           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" 的简写,它是一个表示方法名的类型。在 Objective-C 中,当你发送一个消息给一个对象时,你其实是在告诉这个对象执行一个selector。你可以把selector看作是方法名。每个 selector在 Objective-C 的运行时系统中都有一个唯一的地址,即使在不同的类中定义的完全相同的方法名,它们的 selector 地址也是相同的

IMP:这是"implementation"(执行)的简写,它是一个函数指针,指向方法的实现。在 Objective-C 中,每个类的实例都有一个类对象类对象中存储了类的方法列表。每个方法列表元素是一个结构体,其中包含一个 SEL 和一个 IMP

SEL 是方法名
IMP 是方法的实现

4.2.3举例说明

例如,你有一个 Person 对象,你向这个对象发送一个 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 对象收到 sayHello 消息。
2.运行时系统通过 Person 对象的 isa 指针找到 Person 类。
3.在 Person 类的方法列表中查找 sayHello 的选择器(SEL)。
4.如果找到了,就获取 sayHello 方法的实现(IMP)并执行这个实现。
5.如果没有找到,就在 Person 类的父类的方法列表中查找。

  1. 如果在所有的父类中都找不到,就会触发消息转发(Message Forwarding)机制。
4.2.5举例升级, 如何使用CTMediator 做这个消息发送.
// 创建一个 CTMediator 实例
CTMediator *mediator = [CTMediator sharedInstance];

// 创建一个字典来存储需要传递给 "sayHello" 方法的参数
NSDictionary *params = @{};

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

CTMediator 实例的 performTarget:action:params:shouldCacheTarget: 方法被用来发送 sayHello消息。这个方法的参数如下:

target:这是你想要发送消息的目标对象的名称。在这个例子中,目标对象是 Person。
action:这是你想要执行的方法的名称。在这个例子中,你想要执行的方法是 sayHello。
params:这是一个字典,包含你想要传递给目标方法的参数。在这个例子中,sayHello 方法不需要任何参数,所以这个字典是空的。
shouldCacheTarget:这是一个布尔值,决定是否缓存目标对象。如果设置为 YES,CTMediator 会缓存目标对象,以便下次可以快速找到它。如果设置为 NO,CTMediator 不会缓存目标对象。

当我们调用 performTarget:action:params:shouldCacheTarget: 方法时,CTMediator 会在运行时查找并调用 Person 对象的 sayHello 方法。这就是 CTMediator 的工作原理:它可以在运行时动态地找到并调用任何对象的任何方法,而无需在编译时就确定这些信息。

下面是CTMediator去调用这个方法的流程图.

+-----------------+        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 的情况下,直接调用Person 的方法.

5 参数透传, 且支持 block 回调

使用 CTMediator 的过程中,方法block回调可以被定义为一个 block,然后 将这个 block作为参数传递给目标方法。在目标方法完成后,这个回调 block 将被执行,你可以在回调 block 中接收和处理目标方法的执行结果。

// 创建 Calculator 对象
Calculator *calculator = [[Calculator alloc] init];

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


// Calculator 类的实现
@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 来调用 calculate:withCompletion: 方法并获取计算结果,代码如下:

// 创建一个 CTMediator 实例
CTMediator *mediator = [CTMediator sharedInstance];

// 创建一个字典来存储需要传递给 `calculate:withCompletion:` 方法的参数
NSDictionary *params = @{
    @"input1": @10,
    @"input2": @20,
    @"callback": ^(NSInteger result) {
        NSLog(@"The result is %ld", (long)result);
    }
};

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

创建了一个包含三个键值对的 params 字典:

"input1" 的值是 @10。
"input2" 的值是 @20。
"callback" 的值是一个 block,这个 block 会在 calculate:withCompletion:方法完成后被调用,并接收计算结果作为参数。
performTarget:action:params:shouldCacheTarget:方法被调用时,CTMediator 会动态地找到名为 Calculator 的类,然后在这个类中查找 calculate:withCompletion:方法。如果找到了这个方法,CTMediator 就会创建一个 Calculator 实例,然后调用这个实例的calculate:withCompletion:方法,并把 params字典作为参数传递给这个方法。

在 calculate:withCompletion: 方法中,你可以从 params 字典中取出你需要的参数,例如:

- (void)calculate:(NSDictionary *)params withCompletion:(void (^)(NSInteger))completion {
    NSNumber *input1 = params[@"input1"];
    NSNumber *input2 = params[@"input2"];
    void (^callback)(NSInteger) = params[@"callback"];
    
    // 进行计算
    NSInteger result = input1.integerValue + input2.integerValue;
    
    // 调用回调 block
    if (callback) {
        callback(result);
    }
}

通过这个字典可以将 block 也一起传入.

6. 缓存机制

缓存目标类实例,避免重复初始化,优化性能。

当我们通过 CTMediator 请求一个目标并执行一个动作时,CTMediator 会首先查看是否已经创建并缓存了这个目标的实例。如果已经创建了,那么 CTMediator 就直接使用这个已经创建的实例;如果还没有创建,那么 CTMediator 就会创建一个新的实例,然后把这个新创建的实例缓存起来,以供后续使用。

这种目标缓存的机制可以帮助避免重复初始化目标实例,从而提高程序的性能。

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

    // 尝试从缓存中获取 target 实例
    NSObject *target = [self safeFetchCachedTarget:targetClassString];
    
    if (target == nil) {
        // 如果缓存中没有找到 target 实例,则创建一个新的实例
        Class targetClass = NSClassFromString(targetClassString);
        target = [[targetClass alloc] init];
    }
    
    if (shouldCacheTarget) {
        // 如果 shouldCacheTarget 参数为 YES,则将新创建的 target 实例缓存起来
        [self safeSetCachedTarget:target key:targetClassString];
    }
    
}

safeFetchCachedTarget: 和 safeSetCachedTarget:key: 这两个方法在 CTMediator 中用于获取和设置缓存的目标实例。

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

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

safeFetchCachedTarget: 方法通过给定的键从缓存中获取对应的目标实例。它是线程安全的,因为它使用了 @synchronized 关键字来确保在多线程环境下的安全访问:

self.cachedTarget 这个属性,它是一个 NSMutableDictionary 类型的字典,用于存储缓存的目标实例。这个字典的键是目标类名的字符串形式,而值是对应的目标实例。

7.异常处理

当我们尝试调用一个目标的某个动作时,CTMediator会首先检查这个目标是否存在,然后检查这个目标是否响应这个动作。如果目标不存在或者不响应这个动作,CTMediator 就会调用 NoTargetActionResponseWithTargetString:selectorString:originParams:方法来处理这个异常。在这个方法中,你可以根据你的需要来定义如何处理这种异常,例如,可以输出一个错误提示,或者调用一个备用的目标或动作。

- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
{
    // ... 其他代码 ...
    
    if (target == nil) {
        // 如果目标不存在,则调用 `NoTargetActionResponseWithTargetString:selectorString:originParams:` 方法来处理异常
        [self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
        return nil;
    }
    
    if ([target respondsToSelector:action]) {
        // 如果目标响应这个动作,则正常执行这个动作
        return [self safePerformAction:action target:target params:params];
    } else {
        // 如果目标不响应这个动作,则调用 `NoTargetActionResponseWithTargetString:selectorString:originParams:` 方法来处理异常
        [self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
        return nil;
    }
}
8.容易扩展

CTMediator 作为中介者模式的实现,其核心职责是负责组件之间的通信。但是因为设计比较轻便,比较灵活,CTMediator 也可以被扩展来实现各种辅助方法,从而增强其功能。

核心方法 - (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
{
   // 检查参数的有效性
    if (![self isValidParams:params]) {
        NSLog(@"Invalid parameters: %@", params);
        return nil;
    }
    
 // 检查执行结果
    id result = [self safePerformAction:action target:target params:params];
    if (result == nil) {
        // 如果执行失败,则进行错误处理
        [self handleErrorWithTargetName:targetName actionName:actionName params:params];
    }
}

// 检查参数的有效性
- (BOOL)isValidParams:(NSDictionary *)params {
    // 在这里,我们简单地假设所有的参数都必须是 NSString 类型
    for (id value in params.allValues) {
        if (![value isKindOfClass:[NSString class]]) {
            return NO;
        }
    }
    
    return YES;
}

// 处理错误
- (void)handleErrorWithTargetName:(NSString *)targetName actionName:(NSString *)actionName params:(NSDictionary *)params {
    NSLog(@"Failed to perform action %@ on target %@ with parameters: %@", actionName, targetName, params);
}

最后. 是CTMediator 的源码注释, 认真看看.

// 引入 CTMediator 头文件
#import "CTMediator.h"
// 引入 Objective-C 运行时头文件,用于动态调用方法等
#import <objc/runtime.h>
// 引入 CoreGraphics 头文件,用于处理图形相关的操作
#import <CoreGraphics/CoreGraphics.h>

// 定义一个常量字符串,用于获取 Swift 模块名称
NSString * const kCTMediatorParamsKeySwiftTargetModuleName = @"kCTMediatorParamsKeySwiftTargetModuleName";

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

// 声明一个属性,用于存储已缓存的 target
@property (nonatomic, strong) NSMutableDictionary *cachedTarget;

@end

// CTMediator 的实现
@implementation CTMediator

// 公共方法

// 获取 CTMediator 的单例
+ (instancetype)sharedInstance
{
    // 声明一个静态变量用于存储单例对象
    static CTMediator *mediator;
    // 声明一个 dispatch_once_t 变量,用于保证单例创建的线程安全
    static dispatch_once_t onceToken;
    // 使用 GCD 的 dispatch_once 函数创建单例
    dispatch_once(&onceToken, ^{
        mediator = [[CTMediator alloc] init];
        // 初始化 cachedTarget,避免多线程重复初始化
        [mediator cachedTarget];
    });
    // 返回单例对象
    return mediator;
}

// 通过 URL 执行 action,并将结果通过 completion 回调返回
- (id)performActionWithUrl:(NSURL *)url completion:(void (^)(NSDictionary *))completion
{
    // 检查 url 是否为空或者不是 NSURL 类型
    if (url == nil || ![url isKindOfClass:[NSURL class]]) {
        return nil;
    }
    
    // 创建一个 NSMutableDictionary 用于存储参数
    NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
    // 使用 NSURLComponents 解析 url
    NSURLComponents *urlComponents = [[NSURLComponents alloc] initWithString:url.absoluteString];
    // 遍历所有的参数并存入 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);
    }
    
    // 执行 target-action,并将结果返回
    id result = [self performTarget:url.host action:actionName params:params shouldCacheTarget:NO];
    // 如果有 completion 回调,执行回调
    if (completion) {
        if (result) {
            completion(@{@"result":result});
        } else {
            completion(nil);
        }
    }
    // 返回结果
    return result;
}

// 执行 target-action,并将结果返回
- (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_目标名"
    if (swiftModuleName.length > 0) {
        targetClassString = [NSString stringWithFormat:@"%@.Target_%@", swiftModuleName, targetName];
    } else {
        // 否则 targetClassString 为 "Target_目标名"
        targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
    }
    // 从缓存中获取 target
    NSObject *target = [self safeFetchCachedTarget:targetClassString];
    // 如果缓存中没有 target,创建并缓存 target
    if (target == nil) {
        Class targetClass = NSClassFromString(targetClassString);
        target = [[targetClass alloc] init];
    }

    // 生成 action
    NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
    // 通过 actionString 创建一个 SEL
    SEL action = NSSelectorFromString(actionString);
    
    // 如果 target 不存在,调用 NoTargetActionResponseWithTargetString:selectorString:originParams: 方法处理
    if (target == nil) {
        [self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
        return nil;
    }
    
    // 如果需要缓存 target,将 target 缓存起来
    if (shouldCacheTarget) {
        [self safeSetCachedTarget:target key:targetClassString];
    }

    // 如果 target 能响应 action,执行 action 并返回结果
    if ([target respondsToSelector:action]) {
        return [self safePerformAction:action target:target params:params];
    } else {
        // 如果 target 不能响应 action,尝试调用 notFound: 方法
        SEL action = NSSelectorFromString(@"notFound:");
        if ([target respondsToSelector:action]) {
            return [self safePerformAction:action target:target params:params];
        } else {
            // 如果还是无法响应,调用 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_目标名" 对应的 Class 是否存在
    if (moduleName.length > 0) {
        return NSClassFromString([NSString stringWithFormat:@"%@.Target_%@", moduleName, targetName]) != nil;
    } else {
        // 否则返回 "Target_目标名" 对应的 Class 是否存在
        return NSClassFromString([NSString stringWithFormat:@"Target_%@", targetName]) != nil;
    }
}

// 私有方法

// 处理无法响应 action 的情况
- (void)NoTargetActionResponseWithTargetString:(NSString *)targetString selectorString:(NSString *)selectorString originParams:(NSDictionary *)originParams
{
    // 创建一个 "Action_response:" 的 SEL
    SEL action = NSSelectorFromString(@"Action_response:");
    // 创建一个 "Target_NoTargetAction" 的 target
    NSObject *target = [[NSClassFromString(@"Target_NoTargetAction") alloc] init];
    
    // 创建一个 params 字典,包含原始的参数、target 名和 selector 名
    NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
    params[@"originParams"] = originParams;
    params[@"targetString"] = targetString;
    params[@"selectorString"] = selectorString;
    
    // 执行 action 并传入 params
    [self safePerformAction:action target:target params:params];
}

// 安全执行 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];

    // 根据返回类型,创建一个 NSInvocation,并设置参数、selector 和 target
    // 如果返回类型是 void,执行 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,执行 action 并返回结果
    // 如果返回类型是 id,执行 action 并返回结果
    // 注意,这里省略了具体的代码,需要根据实际的返回类型写出相应的代码

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    // 如果返回类型不是上面的任何一种,直接执行 action 并返回结果
    return [target performSelector:action withObject:params];
#pragma clang diagnostic pop
}

// 获取和设置方法

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

// 从缓存中获取 target
- (NSObject *)safeFetchCachedTarget:(NSString *)key {
    // 使用 @synchronized 来保证线程安全
    @synchronized (self) {
        // 从 cachedTarget 中获取指定的 target
        return self.cachedTarget[key];
    }
}

// 将 target 缓存起来
- (void)safeSetCachedTarget:(NSObject *)target key:(NSString *)key {
    // 使用 @synchronized 来保证线程安全
    @synchronized (self) {
        // 将 target 缓存到 cachedTarget 中
        self.cachedTarget[key] = target;
    }
}

@end

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容