iOS中消息回调Apple提供了如下几种方法:
delegate
delegate属于一对一的回调。这种方式在实际的开发中应用的最多。但是缺点是无法实现一对多的回调。
NSNotification
NSNotification属于全局广播。但无法指定回调方法,而且在实际的开发中应该尽量少用通知,因为这种方式很难管理。
- KVO
KVO
属于一对多的回调。但是仅仅适用于监听属性变更方面。 - Block
Block 也算是一种回调方式,但是如果使用不当可能会引起循环引用问题。并且跟
delegate
一样,同样不具备一对多的功能,优点在于用起来方便。
在实际的开发过程中,我们可能需要即需要类似delegate
那样的回调方式,又想要类似KVO
那样的一对多的功能。这种需求在IM类应用
中很普遍,甚至可以说这样的回调方式是IM类应用
的核心。
这里介绍一种使用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
加入数组中。
上面的代码中核心代码就是消息转发那块代码。也就是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
。这样既解决了循环引用的问题,又能在指定的队列上执行回调。