iOS:利用消息转发机制实现多播委托

iOS中消息回调Apple提供了以下几种方法:node

  1. delegate

    delegate属于一对一的回调。这种方式在实际的开发中应用的最多。可是缺点是没法实现一对多的回调。objective-c

  2. NSNotification

    NSNotification属于全局广播。但没法指定回调方法,并且在实际的开发中应该尽可能少用通知,由于这种方式很难管理。数组

  3. KVO

    KVO属于一对多的回调。可是仅仅适用于监听属性变动方面。async

  4. Block

    Block 也算是一种回调方式,可是若是使用不当可能会引发循环引用问题。而且跟delegate同样,一样不具有一对多的功能,优势在于用起来方便。atom

在实际的开发过程当中,咱们可能须要即须要相似delegate那样的回调方式,又想要相似KVO那样的一对多的功能。这种需求在IM类应用中很广泛,甚至能够说这样的回调方式是IM类应用的核心。spa

这里介绍一种使用OC的消息转发机制来实现多播委托功能的方法。这里先直接贴出实现代码再一一解释。线程

@interface MulticastDelegate : NSObject
-(void)regisetDelegate:(id)delegate;
@end

@implementation MulticastDelegate{
    // delegate数组
    NSMutableArray *delegates;
}
-(id)init{
    self = [super init];
    delegates = [NSMutableArray array];
    return self;
}

// 注册delegate
-(void)regisetDelegate:(id)delegate{
    // 其实就是把delegate存入数组
    [delegates addObject:delegate];
}

// 方法签名
-(NSMethodSignature *)methodSignatureForSelector:(SEL)selector{
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

// 利用消息转发机制对delegate数组进行回调
-(void)forwardInvocation:(NSInvocation *)invocation{
    SEL sel = invocation.selector;
    [delegates enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if([obj respondsToSelector:sel]){
            [invocation invokeWithTarget:obj];
        }
    }];
}
@end
复制代码

这里定义了一个叫作MulticastDelegate的类,这个类专门用于管理多播委托的,而且对外只提供了一个regisetDelegate:方法。而这个方法也很简单,就是将delegate加入数组中。code

上面的代码中核心代码就是消息转发那块代码。也就是methodSignatureForSelectorforwardInvocation两个方法。当咱们对一个对象调用了不存在的方法的时候就会触发消息转发机制,当消息转发进入到forwardInvocation的时候说明已经进入到最后一步,而且系统已经把方法的效用信息所有封装进了NSInvocation中了。这时候只须要将delegate数组中的delegate遍历执行便可。对象

总体的实现代码能够说很简单。下面是使用方式代码,既然叫作MulticastDelegate,那么用的时候确定是要先定义个protocol了。那么第一步就是定义protocol队列

@protocol TestDelegate
-(void)print;
@end
复制代码

而后就是注册回调

id mutiDelegate; // 注意是id类型。
mutiDelegate =[[MulticastDelegate alloc] init];
[mutiDelegate regisetDelegate:self];
复制代码

最后就是调用。这一步的调用跟原来的同样,直接对mutiDelegate调用方法便可。

[mutiDelegate print];
复制代码

在上面的MulticastDelegate的初始化过程当中你应该注意到了,变量mutiDelegateid类型,而不是MulticastDelegate。之因此这样作,是由于只有id类型的变量才能调用任意方法而Xcode不会警告,不然XCode都不能编译。

其实看了上面的代码,你会发现一个,咱们日常设置delegate的时候都是weak属性,可是上面的代码中存入数组中的delegate是strong的,这不是造成循环引用了吗?

另外,在实际的开发过程当中,甚至对回调线程也有要求,好比在注册回调的时候指定回调队列。这样就须要修改回调数组的存放的内容了。

// 定义一个存放delete 和 dispatch_queue_t 的class
@interface MulticastDelegateNode : NSObject
@property (nonatomic,weak,readonly)id delegate;
@property (nonatomic,readonly)dispatch_queue_t queue;
@end

@implementation MulticastDelegateNode
-(id)initWithDelegate:(id)delegate delegateQueue:(dispatch_queue_t)queue{
    self = [super init];
    _delegate = delegate;
    _queue = queue;
    return self;
}
@end


@implementation MulticastDelegate{
    // delegate数组
    NSMutableArray<MulticastDelegateNode *> *delegates;
}
-(id)init{
    self = [super init];
    delegates = [NSMutableArray array];
    return self;
}

// 注册delegate
-(void)regisetDelegate:(id)delegate delegateQueue:(dispatch_queue_t)queue{
    // 其实就是把delegate存入数组
    MulticastDelegateNode *node = [[MulticastDelegateNode alloc] initWithDelegate:delegate delegateQueue:queue?:dispatch_get_main_queue()];
    [delegates addObject:node];
}

// 方法签名
-(NSMethodSignature *)methodSignatureForSelector:(SEL)selector{
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

// 利用消息转发机制对delegate数组进行回调
-(void)forwardInvocation:(NSInvocation *)invocation{
    SEL sel = invocation.selector;
    [delegates enumerateObjectsUsingBlock:^(MulticastDelegateNode * _Nonnull node, NSUInteger idx, BOOL * _Nonnull stop) {
        if([node.delegate respondsToSelector:sel]){
            dispatch_async(node.queue, ^{
                [invocation invokeWithTarget:node.delegate];
            });
        }
    }];
}
@end
复制代码

上面的代码中额外定义了一个MulticastDelegateNode的class,主要是以weak的方式保存delegate,而且保存dispatch_queue_t。这样既解决了循环引用的问题,又能在指定的队列上执行回调。

相关文章
相关标签/搜索