美文网首页ios 开发
iOS消息转发及其应用

iOS消息转发及其应用

作者: 小乙的乙 | 来源:发表于2018-12-15 00:07 被阅读35次

    消息转发原理

    大家都知道OC调用方法,本质上就是发消息,实际上就是调用
    objc_msgSend() 方法,一般情况下,对象可以调用本类,父类,类目的方法。但不能调用其他没有继承关系的类的方法,消息转发可以A类的对象调用B类对象的方法。这种机制虽然打破了类的封装性,但却带来了更多的灵活性。

    来自NSObject 中的消息转发

    1. 当A对象在本类 父类 分类中未找到方法的实现,则去下面两个方法查找,是否有动态创建方法实现。
    //Dynamically provides an implementation for a given selector for a class method  动态提供类方法实现
    + (BOOL)resolveClassMethod:(SEL)sel;
    // Dynamically provides an implementation for a given selector for an instance method// 动态提供一个实例方法的实现
    + (BOOL)resolveInstanceMethod:(SEL)sel;
    
    1. 如果没有在1中找到相应的动态实现,则调用下面的方法进行消息转发。既然本类处理不了,那谁能处理就扔给谁喽,和重定向有点类似
    // Returns the object to which unrecognized messages should first be directed. 返回一个能处理这个无法识别消息的对象
    - (id)forwardingTargetForSelector:(SEL)aSelector
    
    1. 如果2 步骤仍然没能处理,则把该消息扔给消息派发系统,该系统的元素包含:target, selector,参数,返回值。NSInvocation对象可以被重复派发给不同的对象,也可以在偷换selector,但是方法签名要相同。XMPPFrameWork中的GCDMulticastDelegate(多播代理)就是利用这一机制实现的,将会在应用部分介绍。
    // 将返回一个方法签名,同时抛一个异常,unrecognized selector sent to instance ** 这个异常就是在这里抛的,可以在方方法去掉这个异常,代码就遇到不识别的selector 就不会崩了。不建议这么玩。
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
    // 消息派发系统来处理 selector
    - (void)forwardInvocation:(NSInvocation *)anInvocation;
    
    1. 派发系统也无法处理,调用这个doesNotRecognizeSelector方法
    - (void)doesNotRecognizeSelector:(SEL)aSelector
    

    写了个不入流的demo,记录下学习过消息转发。如果想精通消息转发请看腾讯大神的博客,我这篇最多是入门级的。 Objective-C 消息发送与转发机制原理 大神会帮助我们揭秘源码内部是怎么用汇编语言实现消息转发的

    消息转发的应用

    XMPP的多播代理

    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    {
        for (GCDMulticastDelegateNode *node in delegateNodes)
        {     // 兼容Mac
            id nodeDelegate = node.delegate;
            #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
            if (nodeDelegate == [NSNull null])
                nodeDelegate = node.unsafeDelegate;
            #endif
            //匹配能处理代理selector的方法签名,即消息要转发给哪些代理
            NSMethodSignature *result = [nodeDelegate 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
    {
        SEL selector = [origInvocation selector];
        BOOL foundNilDelegate = NO;
        
        for (GCDMulticastDelegateNode *node in delegateNodes)
        {
            id nodeDelegate = node.delegate;
            #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
            if (nodeDelegate == [NSNull null])
                nodeDelegate = node.unsafeDelegate;
            #endif
            
            if ([nodeDelegate respondsToSelector:selector])
            {
                // All delegates MUST be invoked ASYNCHRONOUSLY.
                
                NSInvocation *dupInvocation = [self duplicateInvocation:origInvocation];
                
                dispatch_async(node.delegateQueue, ^{ @autoreleasepool {
                    // 异步执行代理方法
                    [dupInvocation invokeWithTarget:nodeDelegate];
                    
                }});
            }
            else if (nodeDelegate == nil)
            {
                foundNilDelegate = YES;
            }
        }
        
        if (foundNilDelegate)
        {
            // At lease one weak delegate reference disappeared.
            // Remove nil delegate nodes from the list.
            // 
            // This is expected to happen very infrequently.
            // This is why we handle it separately (as it requires allocating an indexSet).
            
            NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] init];
            
            NSUInteger i = 0;
            for (GCDMulticastDelegateNode *node in delegateNodes)
            {
                id nodeDelegate = node.delegate;
                #if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
                if (nodeDelegate == [NSNull null])
                    nodeDelegate = node.unsafeDelegate;
                #endif
                
                if (nodeDelegate == nil)
                {
                    [indexSet addIndex:i];
                }
                i++;
            }
            
            [delegateNodes removeObjectsAtIndexes:indexSet];
        }
    }
    
    • 多播代理使用 用法比较简单,想要通知就添加代理即可。
    - (void)viewDidLoad {
        [super viewDidLoad];
    //     多播代理
        People *p1 = [People new];
        Dog *d1 = [Dog new];
        GCDMulticastDelegate *mutlDelegate = [[GCDMulticastDelegate alloc]init];
        [mutlDelegate addDelegate:p1 delegateQueue:dispatch_get_global_queue(0, 0)];
        [mutlDelegate addDelegate:d1 delegateQueue:dispatch_get_global_queue(0, 0)];
        // mutlDelegate 并没有实现 test1方法 而是通过消息转发分别让 p1 和 d1 处理test1消息 相当于d1 p1 异步调用自己的test1方法
        [mutlDelegate performSelector:@selector(test1)];
    }
    

    参考文档

    相关文章

      网友评论

        本文标题:iOS消息转发及其应用

        本文链接:https://www.haomeiwen.com/subject/tcmghqtx.html