Multicast Delegate 实现分析
Multicast Delegate?
传统 Delegate 大家都知道是一对一关系,一个对象只能给对应 Delegate 对象做回调,而 Multicast Delegate 顾名思义就是能向多个 Delegate 对象进行回调。
Multicast Delegate 与 KVO
对于某些属性值我们可以采用 KVO 的方法进行监听获得最新值(比如 contentOffset
、contentSize
等)。但某些属性值却无法进行 KVO (比如 isDragging
、isTracking
等),原因很简单,可以参考下 KVO 的实现原理,对 readonly
的属性,即不带有 setter
的属性进行 KVO 是无效的。因此 Multicast Delegate 可以很好地解决这种情况。
NSNotification?
Multicast 原理
为了实现同时给多个对象进行回调,也就是执行对应方法,不难想到 Runtime 中消息发送的流程之一:
消息转发
那么 Multicast Delegate 的核心就是 methodSignatureForSelector:
-> forwardInvocation:
-> doesNotRecognizeSelector:
的重新实现。
NSMethodSignature
记录了返回值和方法参数的类型信息。
NSInvocation
记录了 target、selector、parameters 等消息发送所必需的元素。
直接调用某个对象的方法有两种:
performSelector:withObject
NSInvocation
其中第一种方法能实现简单的调用,但无法处理多参数或者返回值。那么使用 NSInvocation
可以解决相对复杂的操作。
GCDMulticastDelegate
GCDMulticastDelegate 为 XMPP 框架中的一个工具类,同时也是 Multicast Delegate 的一个典范。
其中里面包含着这几样东西:
- GCDMulticastDelegateNode
- GCDMulticastDelegateEnumerator
- GCDMulticastDelegate
GCDMulticastDelegateNode 类
GCDMulticastDelegateNode 主要为我们提供一个将原 Delegate 对象包装成 DelegateNode 的初始化方法,同时可以绑定指定的 dispatch queue
。
#if __has_feature(objc_arc_weak)
__weak id delegate;
#if !TARGET_OS_IPHONE
__unsafe_unretained id unsafeDelegate; // Some classes don't support weak references yet (e.g. NSWindowController)
#endif
#else
__unsafe_unretained id delegate;
#endif
dispatch_queue_t delegateQueue;
static BOOL SupportsWeakReferences(id delegate)
{
if ([delegate isKindOfClass:[NSATSTypesetter class]]) return NO;
......
return YES;
}
其中比较有趣的是,源码中有两种源 Delegate,一个是 __weak delegate
,另一个是 __unsafe_unretained unsafeDelegate
,原因在注释中也说得很清楚,某些类不支持 weak 的引用方式,比如:NSWindowController。同时往下看会有 SupportsWeakReferences
用于判断所有不支持 weak 的类。
同时注意如果传入了 delegateQueue
,dispatch_retain
和 dispatch_release
是成对出现的。
GCDMulticastDelegateEnumerator 类
相当简单的一个类,首先看初始化方法:
- (id)initFromDelegateNodes:(NSMutableArray *)inDelegateNodes
{
if ((self = [super init]))
{
delegateNodes = [inDelegateNodes copy];
numNodes = [delegateNodes count];
currentNodeIndex = 0;
}
return self;
}
传入 DelegateNodes 数组并复制一份,同时初始化相关参数。
再根据 @interface
中声明的方法:
@interface GCDMulticastDelegateEnumerator : NSObject
@property (nonatomic, readonly) NSUInteger count;
- (NSUInteger)countOfClass:(Class)aClass;
- (NSUInteger)countForSelector:(SEL)aSelector;
- (BOOL)getNextDelegate:(id _Nullable * _Nonnull)delPtr delegateQueue:(dispatch_queue_t _Nullable * _Nonnull)dqPtr;
- (BOOL)getNextDelegate:(id _Nullable * _Nonnull)delPtr delegateQueue:(dispatch_queue_t _Nullable * _Nonnull)dqPtr ofClass:(Class)aClass;
- (BOOL)getNextDelegate:(id _Nullable * _Nonnull)delPtr delegateQueue:(dispatch_queue_t _Nullable * _Nonnull)dqPtr forSelector:(SEL)aSelector;
@end
可以看出提供的是遍历相关的便利方法。
GCDMulticastDelegate 类
Multicast Delegate 的核心实现,持有一个 DelegateNodes 数组,负责储存 Delegate 对象,内部通过 Runtime 特性实现多 Delegate 的消息转发功能。
Delegate 的增删
- (void)addDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
- (void)removeDelegate:(id)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue;
- (void)removeDelegate:(id)delegate;
- (void)removeAllDelegates;
Delegate 的统计相关
@property (nonatomic, readonly) NSUInteger count;
- (NSUInteger)countOfClass:(Class)aClass;
- (NSUInteger)countForSelector:(SEL)aSelector;
消息转发实现
回到消息转发中 Normal Forwarding 的第一步,创建 method Signature :
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
// 首先从存放 Delegate Node 的数组中开始遍历
for (GCDMulticastDelegateNode *node in delegateNodes)
{
id nodeDelegate = node.delegate; // 获得 Node 中 Delegate 对象
#if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
if (nodeDelegate == [NSNull null])
nodeDelegate = node.unsafeDelegate;
#endif
NSMethodSignature *result = [nodeDelegate methodSignatureForSelector:aSelector]; // 创建对应的方法签名
if (result != nil)
{
return result; // 不为空就直接返回
}
}
// 若最终方法签名为 `nil`,按照正常情况则必定会闪退
// 这个方法回造成崩溃,因为结果同样为 `nil`
// return [super methodSignatureForSelector:aSelector];
// 这个方法也会造成崩溃
// return nil;
// 最终是通过一个空实现的函数(doNothing)来创建函数签名
// 其实这里还可以写成 return [NSMethodSignature signatureWithObjCTypes:"v@:"];
return [[self class] instanceMethodSignatureForSelector:@selector(doNothing)];
}
- (void)doNothing {} // 用于创建函数签名的最后保障
然后第二步,转发 Invocation :
- (void)forwardInvocation:(NSInvocation *)origInvocation
{
SEL selector = [origInvocation selector]; // 需要调用的方法的 SEL
BOOL foundNilDelegate = NO; // 标记空 Delegate 对象
for (GCDMulticastDelegateNode *node in delegateNodes)
{
// 同样的,先遍历获得 Node 中的 delegate 对象
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])
{
// 所有的 Delegate 必须异步调用
NSInvocation *dupInvocation = [self duplicateInvocation:origInvocation];
dispatch_async(node.delegateQueue, ^{ @autoreleasepool {
[dupInvocation invokeWithTarget:nodeDelegate];
}});
}
else if (nodeDelegate == nil)
{
foundNilDelegate = YES;
}
}
// 若 Delegate 已被释放,为 nil,那么就要从数组中移除
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)doesNotRecognizeSelector:(SEL)aSelector
{
// 覆盖实现,防止抛出 NSInvalidArgumentException
}
一个有趣的方法:- (NSInvocation *)duplicateInvocation:(NSInvocation *)origInvocation;
,用于复制 invocation 对象。
为什么要这么做呢。因为每个 Delegate 调用的时候都会去设置 invocation 的 target,同时是异步触发,那么就有可能造成 target 被意外替换掉,会造成 crash,所以复制一个 invocation 还是很有必要的。
总结
GCDMulticastDelegate 的代码量不多,并且可以直接从 GCDMulticastDelegate 直接拿出来使用。其实现考虑的情况相当完善,包括有是否支持 weak
引用的判断、delegate 对应的 dispatch queue
等。