Objective-c Multicast Delegate

NSNotificationCenter 我们肯定都有使用过,通过发送广播,实现一对多的消息发送。NSNotificationCenter使用起来灵活性特别高,但有时候过度使用反而是NSNotificationCenter 的弊端。在项目的设计中,我们也常会思考的一个问题:限制部分灵活性,以此来交换应用的可读性和可维护性。

NSNotificationCenter 允许应用各种跨层访问,监听者要配合合理使用add、remove等方法,出了问题实在不好跟踪。在某些时刻,需要酌情考虑是否应该使用。

对于一对多的消息发送,其实还有很多办法可以考虑。比如说,我们可以通过NSProxy 实现消息转发,将我们普通一对一的delegate模式,改为一对多。

在目前实现过程中,需要考虑的主要有下面三点:

1. 使用NSHashTable 处理循环引用问题

既然是一对多,肯定需要有容器保存delegate指针,出于避免循环引用等问题的考虑,目前使用NSHashTable代替了数组或者字典。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (instancetype)init{
_delegates = [NSHashTable hashTableWithOptions:NSHashTableWeakMemory];
return self;
}

- (void)addDelegate:(id)delegate{
if (delegate != nil) {
[_delegates addObject:delegate];
}else{
NSAssert(NO, @"delegate couldn't be nil");
}
}

- (void)removeDelegate:(id)delegate{
[_delegates removeObject:delegate];
}

- (void)removeAllDelegates{
[_delegates removeAllObjects];
}

2. 使用message forwarding转发消息

-methodSignatureForSelector:-forwardInvocation:两个方法是实现转发的关键,使用NSProxy 只是因为相对于 NSObject,NSProxy更专注于消息转发,没有其他太多无关的方法。当然也可以使用NSObject来做。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
for (id delegate in _delegates) {

NSMethodSignature *result = [delegate methodSignatureForSelector:sel];
if (result != nil) {
return result;
}
}

// This causes a crash...
// return [super methodSignatureForSelector:sel];
return [[self class] instanceMethodSignatureForSelector:@selector(doNothing)];
}

- (void)forwardInvocation:(NSInvocation *)invocation
{
SEL sel = invocation.selector;

for (id delegate in _delegates) {
if ([delegate respondsToSelector:sel]) {
[invocation invokeWithTarget:delegate];
}else{

// This causes a crash...
// [super forwardInvocation:invocation];
[self doNothing];
}
}
}

- (void)doNothing{
}

3. 处理 @optional的协议方法 crash问题

协议不可能都是 @required 肯定还有 @optional,在转发中记得处理可能crash的情况。

使用Multicast Delegate,中间多了一层 protocol 关联,某种情况下可以解决NSNotificationCenter 跨层带来的过于松散等问题。

DEMO 地址