iOS中消息回调Apple提供了以下几种方法:node
delegate
delegate属于一对一的回调。这种方式在实际的开发中应用的最多。可是缺点是没法实现一对多的回调。objective-c
NSNotification
NSNotification属于全局广播。但没法指定回调方法,并且在实际的开发中应该尽可能少用通知,由于这种方式很难管理。数组
KVO
属于一对多的回调。可是仅仅适用于监听属性变动方面。async
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
上面的代码中核心代码就是消息转发那块代码。也就是methodSignatureForSelector
和forwardInvocation
两个方法。当咱们对一个对象调用了不存在的方法的时候就会触发消息转发
机制,当消息转发进入到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
的初始化过程当中你应该注意到了,变量mutiDelegate
是id
类型,而不是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
。这样既解决了循环引用的问题,又能在指定的队列上执行回调。