消息转发与多重代理

代理容器

在开发过程中,我们可能会遇到这种情况,某一处有变量被修改或某个动作执行完毕时,我们需要通知多个对象做出不同的响应。这种情况下,我们可能首先想的就是使用NSNotificationCenter来实现。
如果需求进一步加深,我们需要这些对象在执行完自己的响应之后有返回值回到发出“通知”的地方。这种情况下如果再使用“通知”就会变得很繁琐,我们需要额外的处理很多“通知”。
这种情况下,我们会期望使用“协议-代理”的方式来进行交互处理,但一般情况下“协议-代理”只被用在一对一的交互关系中,无法适用于此处的一对多关系。那么我们该怎么做呢?

我们来设想如果使用一个容器,来把所有“代理”都装在里面,当我们需要使用回调时,再把它们一个个拿出来进行回调,不就解决了这个问题吗。

    int result = 0;
    for (id delegate in self.delegateArray) {
      result += [delegate doSomething];
    }

这样当然可以解决我们最初的问题,但是我们肯定不想在每个需要代理的地方都维护一个数组,并且每次回调时都手写一个循环。所以,我们需要一个新的类来帮助我们管理这个容器。我们姑且叫这个代理管理类为代理容器。

代理容器帮助我们维护了代理数组,但是当我们需要回调时,代理容器如何找到数组里正确的代理,并告诉它调用哪个方法呢,这就需要进行’消息转发‘。

消息转发

我们知道Object—C的方法调用,其实就是依托objc_msgSend()给对象发送了一条消息。对象在收到消息之后,会通过isa找到类的结构体,然后读取method list寻找selector。如果在本类中找不到,则去他的父类的结构体寻找,一直到NSObject为止。如果在NSObject也没有找到需要的selector,这时候就需要开始进行消息转发了。

消息转发的过程被总结为三个步骤,分别是:

1.Method resolution 方法解析处理阶段
2.Fast forwarding 快速转发阶段
3.Normal forwarding 常规转发阶段
Method resolution 方法解析处理阶段
 //实例对象调用方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
 //类对象调用方法
+ (BOOL)resolveClassMethod:(SEL)sel

对象在method list中找不到对应selector时,会先调用这两个方法。你可以将它理解成,询问你是否添加动态方法来进行处理;如果YES则能接受消息表示你已经添加了方法,将会重新执行objc_msgSend;NO则表示不添加方法,进入消息转发第二步。

Fast forwarding 快速转发阶段
- (id)forwardingTargetForSelector:(SEL)aSelector

既然在第一步已经明确,此对象没有添加新方法响应aSelector,这个方法实际上是让你传递一个可以响应此方法的其它对象。如果你在此返回了一个对象,则会给这个对象发送一个objc_msgSend消息,如果这里返回nil或者self本身,那么则进入消息转发的第三个步骤。

Normal forwarding 常规转发阶段
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- (void)forwardInvocation:(NSInvocation *)anInvocation

在第二步已经明确没有其他对象可以帮助我们响应这个方法,所以第三部Objec-C又做出了让步。你可以重新制定一个方法,但前提是你需要给出这个方法的方法签名。NSMethodSignature表示方法签名,手写创建比较麻烦,你可以找一下它的规则表。如果你在这里返回了一个方法签名,那么我们还需要在forwardInvocation中来定义如何使用这个方法签名。

至此我们完成了消息转发的所有步骤,如果在这三个步骤都没有处理好找个SEL,那么就会执行doesNotRecognizeSelector方法抛出一个异常。

NSMethodSignature && NSInvocation

这里出现了 NSMethodSignature 和 NSInvocation 这两个类。 这里也稍微聊一下NSInvocation这个类。

我们平时调用方法的写法可能有两种:

    [anObject doSomething];
    [anObject performSelector:@selector(doSomething) withObject:anArgument];

在不同的情况下,我们会选择合适的调用方法。在此之后,OC还为我们提供了另一种调用方法,就是使用NSInvocation来进行。

//    1.创建方法签名;
    NSMethodSignature *sig = [[self class] instanceMethodSignatureForSelector:@selector(doSomething:)];
//    2.通过方法签名生成NSInvocation
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
//    3.对invocation设置 方法调用者
    invocation.target = self;
//    4.对invocation设置 方法选择器
    invocation.selector = @selector(doSomething:);
//    5.对invocation设置 参数
    NSString *something = @"do something";
    //注意:设置的参数必须从2开始;因为0和1 已经被self ,_cmd 给占用了
    [invocation setArgument:&something atIndex:2];
//    6.执行invocation
    [invocation invoke];

NSInvocation相关的API还有许多,这里就不赘述了。

GCDMulticastDelegate

我们从XMPP框架里的GCDMulticastDelegate来看一下多重代理的具体实现。

GCDMulticastDelegate中共有 GCDMulticastDelegate、GCDMulticastDelegateEnumerator、GCDMulticastDelegateNode这三个类。我们只要重点看一下GCDMulticastDelegate这个类就可以了。

GCDMulticastDelegate

GCDMulticastDelegate维护了一个delegateNodes数组,当调用addDelegate方法时,就会把真正的代理’delegate‘和一个GCD线程构建成一个GCDMulticastDelegateNode对象添加到数组里面。

- (id)init
{
    if ((self = [super init]))
    {
        delegateNodes = [[NSMutableArray alloc] init];
    }
    return self;
}

- (void)addDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue
{
    if (delegate == nil) return;
    if (delegateQueue == NULL) return;
    
    GCDMulticastDelegateNode *node = [[GCDMulticastDelegateNode alloc] init];
    node.delegate = delegate;
    node.delegateQueue = delegateQueue;
    
    [delegateNodes addObject:node];
}

当我们需要调用代理的协议方法时,我们实际上就是让GCDMulticastDelegate的对象执行某方法。很明显,GCDMulticastDelegate是并没有实现这个方法的。所以接下来我们就要进入消息转发的三个阶段。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    for (GCDMulticastDelegateNode *node in delegateNodes)
    {
        NSMethodSignature *result = [node.delegate methodSignatureForSelector:aSelector];
        
        if (result != nil)
        {
            return result;
        }
    }
    
    // This causes a crash...
    // return [super methodSignatureForSelector:aSelector];
    
    // This also causes a crash...
    // return nil;
    
    return [[self class] instanceMethodSignatureForSelector:@selector(doNothing)];
}

- (void)forwardInvocation:(NSInvocation *)origInvocation
{
    @autoreleasepool {
    
        SEL selector = [origInvocation selector];
        
        for (GCDMulticastDelegateNode *node in delegateNodes)
        {
            id delegate = node.delegate;
            
            if ([delegate respondsToSelector:selector])
            {
                // All delegates MUST be invoked ASYNCHRONOUSLY.
                
                NSInvocation *dupInvocation = [self duplicateInvocation:origInvocation];
                
                dispatch_async(node.delegateQueue, ^{ @autoreleasepool {
                    
                    [dupInvocation invokeWithTarget:delegate];
                    
                }});
            }
        }
    }
}

- (NSInvocation *)duplicateInvocation:(NSInvocation *)origInvocation
{
    NSMethodSignature *methodSignature = [origInvocation methodSignature];
    
    NSInvocation *dupInvocation = [NSInvocation invocationWithMethodSignature:methodSignature];
    [dupInvocation setSelector:[origInvocation selector]];
    
    NSUInteger i, count = [methodSignature numberOfArguments];
    for (i = 2; i < count; i++)
    {
        const char *type = [methodSignature getArgumentTypeAtIndex:i];
        
        if (*type == *@encode(BOOL))
        {
            BOOL value;
            [origInvocation getArgument:&value atIndex:i];
            [dupInvocation setArgument:&value atIndex:i];
        }
        else if (*type == *@encode(char) || *type == *@encode(unsigned char))
        {
            char value;
            [origInvocation getArgument:&value atIndex:i];
            [dupInvocation setArgument:&value atIndex:i];
        }
        else if (*type == *@encode(short) || *type == *@encode(unsigned short))
        {
            short value;
            [origInvocation getArgument:&value atIndex:i];
            [dupInvocation setArgument:&value atIndex:i];
        }
        else if (*type == *@encode(int) || *type == *@encode(unsigned int))
        {
            int value;
            [origInvocation getArgument:&value atIndex:i];
            [dupInvocation setArgument:&value atIndex:i];
        }
        else if (*type == *@encode(long) || *type == *@encode(unsigned long))
        {
            long value;
            [origInvocation getArgument:&value atIndex:i];
            [dupInvocation setArgument:&value atIndex:i];
        }
        else if (*type == *@encode(long long) || *type == *@encode(unsigned long long))
        {
            long long value;
            [origInvocation getArgument:&value atIndex:i];
            [dupInvocation setArgument:&value atIndex:i];
        }
        else if (*type == *@encode(double))
        {
            double value;
            [origInvocation getArgument:&value atIndex:i];
            [dupInvocation setArgument:&value atIndex:i];
        }
        else if (*type == *@encode(float))
        {
            float value;
            [origInvocation getArgument:&value atIndex:i];
            [dupInvocation setArgument:&value atIndex:i];
        }
        else if (*type == '@')
        {
            void *value;
            [origInvocation getArgument:&value atIndex:i];
            [dupInvocation setArgument:&value atIndex:i];
        }
        else
        {
            NSString *selectorStr = NSStringFromSelector([origInvocation selector]);
            
            NSString *format = @"Argument %lu to method %@ - Type(%c) not supported";
            NSString *reason = [NSString stringWithFormat:format, (unsigned long)(i - 2), selectorStr, *type];
            
            [[NSException exceptionWithName:NSInvalidArgumentException reason:reason userInfo:nil] raise];
        }
    }
    
    [dupInvocation retainArguments];
    
    return dupInvocation;
}

从这几个方法中,我们可以出其处理消息转发的逻辑

1. 常规消息转发阶段,在寻找NSMethodSignature方法签名时,会遍历储存Node的数组,寻找可以执行此方法的delegate,获取其方法签名。
2. 在forwardInvocation方法中,首先我们获取到selector,然后遍历数组,寻找可以实现此selector的对象。
3. 利用方法签名生成了一个新的NSNSInvocation对象,然后遍历原origInvocation的参数列表,将所有的参数及其类型押入新的NSInvocation当中。
4. 在Node内存储的GCD线程中,异步调用Invocation的invoke方法,传入参数delegate作为执行这个方法的对象。

这样我们就完成了将一个GCDMulticastDelegate对象无法响应的方法转发给其持有的所有能影响此方法的对象,并在其指定的线程中进行回调。

你可能感兴趣的:(消息转发与多重代理)