iOS消息转发及其应用

消息转发原理

大家都知道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的多播代理

  • 一般情况,代理只能1V1通信,开源XMPPFramework项目中 GCDMulticastDelegate类使用消息转发实现了多播代理,主要代码:
- (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)];
}
  • demo代码

参考文档

  • Objective-C 消息发送与转发机制原理 腾讯大神哦

你可能感兴趣的:(iOS消息转发及其应用)