2019年10月14日 更新源码

探究NSNotificationCenter的实现并尝试自实现。


一、同步通知

接口

NSNotification并不复杂,封装了name、object以及userInfo,使用指定构造器初始化即可。

1
2
3
4
5
6
7
8
9
10
11
12
@interface NSNotification : NSObject <NSCopying, NSCoding>
@property (readonly, copy) NSNotificationName name;
@property (nullable, readonly, retain) id object;
@property (nullable, readonly, copy) NSDictionary *userInfo;
- (instancetype)initWithName:(NSNotificationName)name object:(nullable id)object userInfo:(nullable NSDictionary *)userInfo;
- (nullable instancetype)initWithCoder:(NSCoder *)coder;
@end

@interface NSNotification (NSNotificationCreation)
+ (instancetype)notificationWithName:(NSNotificationName)aName object:(nullable id)anObject;
+ (instancetype)notificationWithName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;
@end

NSNotificationCenter则给我们提供了添加通知接收者、发送通知的接口。NSNotificationName就是NSString *,defaultCenter是单例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@interface NSNotificationCenter : NSObject {
@package
void *_impl;
void *_callback;
void *_pad[11];
}

@property (class, readonly, strong) NSNotificationCenter *defaultCenter;

- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;

- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;

- (void)removeObserver:(id)observer;
- (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject;

- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block;
// The return value is retained by the system, and should be held onto by the caller in order to remove the observer with removeObserver: later, to stop observation.
@end

添加observer

1
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;

最常用的就是这个方法,指定observer、selector、name以及object即可。它做了什么呢?
苹果官方文档解释如下:

Adds an entry to the notification center’s dispatch table with an observer and a notification selector, and an optional notification name and sender.
Parameters

observer: Object registering as an observer.

aSelector: Selector that specifies the message the receiver sends observer to notify it of the notification posting. The method specified by aSelector must have one and only one argument (an instance of NSNotification).

aName: The name of the notification for which to register the observer; that is, only notifications with this name are delivered to the observer.
If you pass nil, the notification center doesn’t use a notification’s name to decide whether to deliver it to the observer.

anObject: The object whose notifications the observer wants to receive; that is, only notifications sent by this sender are delivered to the observer.
If you pass nil, the notification center doesn’t use a notification’s sender to decide whether to deliver it to the observer.

If your app targets iOS 9.0 and later or macOS 10.11 and later, you don’t need to unregister an observer in its dealloc method. Otherwise, you should call removeObserver:name:object: before observer or any object passed to this method is deallocated

简单解释就是,把必需的observer、selector和可选的name、object注册到通知中心。这个selector有且只有一个参数,参数是NSNotification对象。若指定了通知的name,则observer必须匹配相同的name通知中心才会把消息分发给它,同样,若指定了通知的object,则observer必须匹配相同的object通知中心才会把消息分发给它。在iOS9.0或者macOS 10.11之后,咱们不必要在observer的dealloc方法中取消注册了(现在基本都要求最低是iOS9了)。

再看看坊间GNU的解释:

Registers observer to receive notifications with the name
notificationName and/or containing object (one or both of these two must be
non-nil; nil acts like a wildcard). When a notification of name name
containing object is posted, observer receives a selector message with this
notification as the argument. The notification center waits for the
observer to finish processing the message, then informs the next registree
matching the notification, and after all of this is done, control returns
to the poster of the notification. Therefore the processing in the
selector implementation should be short.

The notification center does not retain observer or object. Therefore,
you should always send removeObserver: or removeObserver:name:object: to
the notification center before releasing these objects.

As a convenience, when built with garbage collection, you do not need to
remove any garbage collected observer as the system will do it implicitly.

NB. For MacOS-X compatibility, adding an observer multiple times will
register it to receive multiple copies of any matching notification, however
removing an observer will remove all of the multiple registrations.

我们得到的重要信息是:

  1. 通知中心会等待所有符合条件的observer把通知消息处理完毕之后才会return,也即发送通知是同步的。这要求我们处理通知消息的逻辑应该“简短”、不会长时间阻塞。
  2. 通知中心并不增加observer和object的引用计数,建议我们适时removeObserver。
  3. 注册完全一致的observer信息(含name、object、selector)多次,回调也是有多次的,而只移除一次完全一致的observer信息,却会把所有的observer信息都移除掉。

对于第三条这么理解,注册3次,回调也是有三次;而移除一次,所有的该通知都被移除了:

1
2
3
4
5
6
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(dealWithNoti:) name:name object:obj];
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(dealWithNoti:) name:name object:obj];
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(dealWithNoti:) name:name object:obj];


[NSNotificationCenter.defaultCenter removeObserver:self name:name object:obj];

源码逻辑是这样的:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
- (void)addObserver:(id)observer
selector:(SEL)selector
name:(NSString *)name
object:(id)object {
Observation *list;
Observation *o;
NSIMapTable m;
NSIMapNode n;

// 加锁
lockNCTable(_table);
// 根据observer和selector获取Observation
o = obsNew(_table, selector, observer);

if (name) {
// 根据name在named表中搜索对应的NSIMapNode
n = NSIMapNodeForKey(_table->named, (NSIMapKey)(id)name);

if (n == 0) { // 没有在named表中找到
// 优先在_table的缓存中查找, 找不到则创建
m = mapNew(_table);
// 将name转换为不可变的name
name = [name copyWithZone:NSDefaultMallocZone()];
// 把name作为key, 将新创建的m(NSIMapTable)设置到named表中
NSIMapAddPair(_table->named, (NSIMapKey)(id)name, (NSIMapVal)(void *)m);
NS_CONSUMED(name)
} else {
// 在named表中找到了, 取出NSIMapNode对应的value
m = (NSIMapTable)n->value.ptr;
}

// object作为key, 取出其在m表中对应的NSIMapNode
n = NSIMapNodeForSimpleKey(m, (NSIMapKey)object);

if (n == 0) {
o->next = -1;
// m表中取不到object对应的值, 把Observation直接存到m表中
NSIMapAddPair(m, (NSIMapKey)object, (NSIMapVal)o);
} else {
// object作为key, 取出其在m表中对应的NSIMapNode, 获取node中的链表
list = (Observation *)n->value.ptr;
// 把Observation追加到链表的最前面
o->next = list->next;
list->next = o;
}
} else if (object) {
// name不存在, 则从nameless表中搜索对应的NSIMapNode
n = NSIMapNodeForSimpleKey(_table->nameless, (NSIMapKey)object);

// 同样的逻辑, 把Observation追加到链表的最前面
if (n == 0) {
o->next = ENDOBS;
NSIMapAddPair(_table->nameless, (NSIMapKey)object, (NSIMapVal)o);
} else {
list = (Observation *)n->value.ptr;
o->next = list->next;
list->next = o;
}
} else {
// name和object都为空, 把Observation追加到wildcard表的最前面
o->next = _table->wildcard;
_table->wildcard = o;
}

// 解锁
unlockNCTable(_table);
}

named表的结构大致如此:

而nameless表则更简单,object作为key,observer数组作为value。

还有一种是以block的形式添加观察者:

1
2
3
4
- (id)addObserverForName:(NSString *)name
object:(id)object
queue:(NSOperationQueue *)queue
usingBlock:(NSNotificationBlock)block

不需要设置selector,还能方便地在block中执行。

1
2
3
4
5
6
7
8
9
10
11
12
- (id)addObserverForName:(NSString *)name
object:(id)object
queue:(NSOperationQueue *)queue
usingBlock:(NSNotificationBlock)block {
NSNotificationObserver *observer =
[[NSNotificationObserver alloc] initWithQueue:queue block:block];
[self addObserver:observer
selector:@selector(didReceiveNotification:)
name:name
object:object];
return observer;
}

从源码来看,这个方法实际上也是调用了addObserver:selector:name:object方法。observer没有变,但是把selector替换成了didReceiveNotification:,它的实现是这样的:

1
2
3
4
5
6
7
8
9
- (void)didReceiveNotification: (NSNotification *)notif {
if (_queue != nil) {
NSNotificationBlockOperation *op = [[NSNotificationBlockOperation alloc]
initWithNotification:notif block:_block];
[_queue addOperation:op];
} else{
CALL_BLOCK(_block, notif);
}
}

didReceiveNotification:方法处理了Queue的逻辑。如果Queue不存在,则直接调用block。如果Queue是存在的,继承自NSOperation的NSNotificationBlockOperation封装了需要回调的block和回传的NSNotification,并在main方法中调用了这个block。最后,这个operation被添加到指定的Queue中执行。

发送通知

1
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;

发送通知最终是调用这个方法。

1
2
3
4
5
6
7
8
9
10
- (void)postNotificationName:(NSString *)name
object:(id)object
userInfo:(NSDictionary *)info {
NSNotification *notification;
notification = (id)NSAllocateObject(concrete, 0, NSDefaultMallocZone());
notification->_name = [name copyWithZone: [self zone]];
notification->_object = [object retain];
notification->_info = [info retain];
[self _postAndRelease: notification];
}

关键的是_postAndRelease:的实现:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
- (void)_postAndRelease:(NSNotification *)notification {
Observation *o;
unsigned count;
NSString *name = [notification name];
id object;
GSIMapNode n;
GSIMapTable m;
GSIArrayItem i[64];
GSIArray_t b;
GSIArray a = &b;
// 获取object
object = [notification object];

GSIArrayInitWithZoneAndStaticCapacity(a, _zone, 64, i);
lockNCTable(_table);

// 获取没有指定name也没有指定object的所有observer
for (o = _table->wildcard = purgeCollected(WILDCARD); o != ENDOBS; o = o->next) {
GSIArrayAddItem(a, (GSIArrayItem)o);
}

// 获取指定object但是没有指定name的所有observer
if (object) {
n = GSIMapNodeForSimpleKey(_table->nameless, (GSIMapKey)object);
if (n != 0) {
o = ((Observation *)n->value.ext)
while (o != (Observation *)-1) {
GSIArrayAddItem(a, (GSIArrayItem)o);
o = o->next;
}
}
}

// 获取指定name而且object也与通知要求的object相匹配的所有observer
if (name) {
n = GSIMapNodeForKey(_table->named, (GSIMapKey)((id)name));
if (n) {
m = (GSIMapTable)n->value.ptr;
} else {
m = 0;
}

if (m != 0) {
// object为空
n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
if (n != 0) {
o = ((Observation *)n->value.ext)
while (o != (Observation *)-1) {
GSIArrayAddItem(a, (GSIArrayItem)o);
o = o->next;
}
}
// object不为空, 且匹配
if (object != nil) {
n = GSIMapNodeForSimpleKey(m, (GSIMapKey)nil);
if (n != 0) {
o = ((Observation *)n->value.ext)
while (o != (Observation *)-1) {
GSIArrayAddItem(a, (GSIArrayItem)o);
o = o->next;
}
}
}
}
}

// 解锁
unlockNCTable(TABLE);

// 遍历数组a, 逐个发送通知
count = GSIArrayCount(a);
while (count-- > 0) {
o = GSIArrayItemAtIndex(a, count).ext;
if (o->next != 0) {
[o->observer performSelector:o->selector withObject:notification];
}
}

// 置空数组a
lockNCTable(_table);
GSIArrayEmpty(a);
unlockNCTable(_table);
// release通知对象
RELEASE(notification);
}

关键在于从表中获取符合要求的Observation:在wildcard表中获取匿名且没有指定object的Observation;在nameless表中获取匹配object但是匿名的Observation;在named表中获取object为空的Observation和object匹配的Observation。把这些Observation添加到一个数组中,遍历,逐个调用selector。

那么,移除通知自然也是根据name、object、observer,找到匹配的Observation对象,从相应的表中移除,不再赘述。

二、异步通知

1
2
3
4
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle;
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle coalesceMask:(NSNotificationCoalescing)coalesceMask forModes:(nullable NSArray<NSRunLoopMode> *)modes;

- (void)dequeueNotificationsMatching:(NSNotification *)notification coalesceMask:(NSUInteger)coalesceMask;

通过NSNotificationQueue可以实现异步通知。NSNotificationCoalescing提供了聚合选项,不聚合、根据name聚合以及根据发送者聚合,也即如果在队列中已有该种通知,如果满足选项,则不会进入队列,只保留第一个通知。NSPostingStyle则可以设置通知发送的时机,立即同步发送、尽可能快地发送,以及在Runloop空闲时发送。

场景如下:
NSPostWhenIdle:比如当用户正在输入文字,需要在某个控件上实时展示文字的长度的时候,可以用到这个选项。
NSPostNow:实时发送通知,但是与同步通知相比,关键在于聚合选项:队列中已有满足选项的通知时,是否只保留一个。
NSPostASAP:多个通知进入到缓冲区后,利用聚合选项,只留下一个,并尽可能快地发送通知。

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
- (void)enqueueNotification:(NSNotification *)notification
postingStyle:(NSPostingStyle)postingStyle
coalesceMask:(NSUInteger)coalesceMask
forModes:(NSArray *)modes {
if (modes == nil) modes = defaultMode; // 默认模式
if (coalesceMask != NSNotificationNoCoalescing) {
// 需要聚合, 则先把队列中的相应的notification移除
[self dequeueNotificationsMatching:notification
coalesceMask:coalesceMask];
}

switch (postingStyle) {
// 同步发送
case NSPostNow: {
NSString *mode = [[NSRunLoop currentRunLoop] currentMode];
// 要求当前Runloop的mode需要匹配, 不然无效
if (mode == nil || [modes indexOfObject:mode] != NSNotFound) {
[_center postNotification: notification];
}
} break;
// 尽可能早发送(as soon as possible)
case NSPostASAP:
add_to_queue(_asapQueue, notification, modes, _zone); break;
// 在Runloop空闲时发送
case NSPostWhenIdle:
add_to_queue(_idleQueue, notification, modes, _zone); break;
}
}

从入口方法来看,关键在于add_to_queue()函数,根据NSPostingStyle选项,把通知添加到_asapQueue队列或者_idleQueue队列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void add_to_queue(NSNotificationQueueList *queue,
NSNotification *notification,
NSArray *modes, NSZone *_zone) {
NSNotificationQueueRegistration *item;
item = NSZoneCalloc(_zone, 1, sizeof(NSNotificationQueueRegistration));
item->notification = RETAIN(notification);
item->name = [notification name];
item->object = [notification object];
item->modes = [modes copyWithZone: [modes zone]];
item->next = NULL;
item->prev = queue->tail;
queue->tail = item;
if (item->prev) item->prev->next = item;
if (!queue->head) queue->head = item;
}

可以看到,把notification和modes封装成NSNotificationQueueRegistration,并把它插入到双向链表queue的尾部。

以NSPostWhenIdle选项为例,在Runloop的acceptInputForMode:beforeDate:方法中找到了通知的发送痕迹:GSPrivateNotifyIdle(mode)。

1
2
3
4
5
6
7
8
9
10
11
void GSPrivateNotifyIdle(NSString *mode) {
NotificationQueueList *item;
for (item = currentList(); item; item = item->next) {
if (item->queue) {
notify(item->queue->_center,
item->queue->_idleQueue,
mode,
item->queue->_zone);
}
}
}

接下来就很明朗了,notify()的逻辑是这样的:

  1. 从双向链表list中取出所有的结点对象NSNotificationQueueRegistration,并把它放到一个数组中。
  2. 遍历该数组,逐个从list中移除:remove_from_queue(list, item, zone);
  3. 遍历该数组,逐个发送通知[NSNotificationCenter.defaultCenter postNotification:notification];,而postNotification:方法本质上还是调用同步通知中的_postAndRelease:方法。

什么时候发送,交由Runloop处理。

三、子线程通知

子线程通知苹果给出了标准的解决方案:利用NSMachPort。假定在A线程发送通知,需要在B线程处理通知,那么B线程中注册NSMachPort,A线程中使用此port发送通知,则B线程就能收到消息并进行处理。

简单概述实现思路就是:维护一个NSNotification数组,接收到通知时,如果通知分发的线程与期望的线程(处理通知的线程)不一致,则把这个通知添加到NSNotification数组中,并利用NSMachPort发送一个“信号”到期望的线程(也就是创建NSMachPort对象的线程)中,在期望的线程中收到信号后,把通知从NSNotification数组中移除,并处理。

比如,需要在主线程发送通知,而在子线程接收并处理通知,结合苹果给出的示例代码,可以这么做:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// 通知名称
const NSNotificationName YADemoNotification = @"YADemoNotification";

@interface YAThreadedNotificationHandler: NSObject <NSMachPortDelegate>
@property (nonatomic, strong) NSMutableArray *notifications;
@property (nonatomic, strong) NSThread *notificationThread;
@property (nonatomic, strong) NSLock *notificationLock;
@property (nonatomic, strong) NSMachPort *notificationPort;
- (void)setUpThreadingSupport;
- (void)processNotification:(NSNotification *)notification;
// Mach port delegate
- (void)handleMachMessage:(void *)msg;
@end
@implementation YAThreadedNotificationHandler
- (instancetype)init {
if (self = [super init]) {
[self setUpThreadingSupport];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(processNotification:) name:YADemoNotification object:nil];
}
return self;
}

- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

#pragma mark - Private methods
- (void)setUpThreadingSupport {
if (self.notifications) return;
self.notifications = [[NSMutableArray alloc] init];
self.notificationLock = [[NSLock alloc] init];
// 配置期望线程(当前线程)
self.notificationThread = [NSThread currentThread];

// 配置端口消息处理的线程(当前线程)
self.notificationPort = [[NSMachPort alloc] init];
[self.notificationPort setDelegate:self];
[[NSRunLoop currentRunLoop] addPort:self.notificationPort forMode:NSRunLoopCommonModes];
}

// 端口代理
- (void)handleMachMessage:(void *)msg {
[self.notificationLock lock];
while ([self.notifications count]) {
NSNotification *notification = [self.notifications objectAtIndex:0];
[self.notifications removeObjectAtIndex:0];
[self.notificationLock unlock];
[self processNotification:notification];
[self.notificationLock lock];
};
[self.notificationLock unlock];
}

// 处理通知
- (void)processNotification:(NSNotification *)notification {
if ([NSThread currentThread] != self.notificationThread) {
// Forward the notification to the correct thread.
[self.notificationLock lock];
[self.notifications addObject:notification];
[self.notificationLock unlock];
[self.notificationPort sendBeforeDate:[NSDate date]
components:nil
from:nil
reserved:0];
}
else {
// Process the notification here;
NSLog(@"处理通知在%@线程", NSThread.currentThread);
}
}
@end

在控制器中,这样发送通知:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@interface ViewController ()
@property (nonatomic, strong) NSThread *thread;
@property (nonatomic, strong) YAThreadedNotificationHandler *notificationHandler;
@end

@implementation ViewController
- (void)startThread {
self.notificationHandler = [YAThreadedNotificationHandler new];
// 主线程发送通知
dispatch_async(dispatch_get_main_queue(), ^{
[NSNotificationCenter.defaultCenter postNotificationName:YADemoNotification object:nil];
});
// 子线程手动开启Runloop
[NSRunLoop.currentRunLoop run];
}

- (void)viewDidLoad {
[super viewDidLoad];

self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(startThread) object:nil];
[self.thread start];
}

四、自实现

有几个弱引用容器在日常工作中没有用过,但是又特别想亲手实践一番。偶尔看到一篇文章,说是自己实现了系统中通知中心的功能,出于好奇,自己也尝试一番,便在这里模仿了同步通知的实现。

主要原理就是创建一个字典observerMap,notification name作为key,NSMapTable作为value。在这个NSMapTable中,object作为key,用于存放observer的NSHashSet容器作为value。

接口文件

接口与系统保持一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

typedef NSString *YANotificationName NS_EXTENSIBLE_STRING_ENUM;

@interface YANotificationCenter : NSObject
@property (class, readonly, strong) YANotificationCenter *defaultCenter;

- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable YANotificationName)aName object:(nullable id)anObject;

- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;

- (void)removeObserver:(id)observer;
- (void)removeObserver:(id)observer name:(nullable YANotificationName)aName object:(nullable id)anObject;

@end
NS_ASSUME_NONNULL_END

Checklist

  • 不增加observer和object的引用计数
  • 支持携带参数信息userInfo
  • 可以不指定object
  • 添加观察者时指定object,post通知时只有相同的object才会有效
  • observer为nil,发送通知时没有作用
  • 指定线程发送通知,则在指定线程调用
  • 没有及时移除通知,observer销毁之后继续发送通知不会产生异常
    (从iOS 9开始,即使不移除观察者对象,程序也不会出现异常。)
  • 支持多个observer监听同一个通知
  • 如果notificationName为nil,object有值,则接收所有指定为object的通知
  • 如果notificationName为nil,且object也为nil,则接收系统内所有通知
    (也即YANotificationCenter发出的所有通知)
  • 移除所有通知后,相关方法不再调用
  • 移除指定通知后,相关方法不再调用
  • 同步处理通知消息
  • 多次添加observer,发送通知时多次调用
  • 支持异步发布通知(NotificationQueue)

功能测试

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
1. 不对observer和object强引用
2. 可以携带参数userInfo
3. 添加观察者时object为nil,post通知时object有值,依然有效(表示不限于指定的object)
[YANotificationCenter.defaultCenter addObserver:a selector:@selector(print3:) name:@"Noti_1" object:nil];
[YANotificationCenter.defaultCenter postNotificationName:@"Noti_1" object:self userInfo:@{@"key": @"value"}];
4. 添加观察者时指定object,post通知时相同的object,有效,不同的object,无效
// 有效
[YANotificationCenter.defaultCenter addObserver:a selector:@selector(print3:) name:@"Noti_2" object:self];
[YANotificationCenter.defaultCenter postNotificationName:@"Noti_2" object:self userInfo:@{@"key": @"value"}];

// 无效
[YANotificationCenter.defaultCenter addObserver:a selector:@selector(print3:) name:@"Noti_3" object:self];
[YANotificationCenter.defaultCenter postNotificationName:@"Noti_3" object:a userInfo:@{@"key": @"value"}];

5. 可以添加n次observer,发送通知时对应调用n次(不支持)
[YANotificationCenter.defaultCenter addObserver:a selector:@selector(print3:) name:@"Noti_4" object:nil];
[YANotificationCenter.defaultCenter addObserver:a selector:@selector(print3:) name:@"Noti_4" object:nil];
[YANotificationCenter.defaultCenter postNotificationName:@"Noti_4" object:nil userInfo:@{@"key": @"value"}];
6. 未添加observer,发送通知时没有作用
[YANotificationCenter.defaultCenter postNotificationName:@"Noti_5" object:nil userInfo:@{@"key": @"value"}];

7. 指定线程发送通知,则在指定线程调用
// print方法是在发送通知的线程中调用的
[YANotificationCenter.defaultCenter addObserver:self selector:@selector(print) name:@"Noti_6" object:nil];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"当前线程:%@",[NSThread currentThread]);
[YANotificationCenter.defaultCenter postNotificationName:@"Noti_6" object:nil userInfo:@{@"key": @"value"}];
});

8. 没有在observer的dealloc方法中移除通知,observer销毁之后继续发送通知,无影响(从iOS 9开始,即使不移除观察者对象,程序也不会出现异常。)
[YANotificationCenter.defaultCenter addObserver:a selector:@selector(print) name:@"Noti_7" object:nil];
// a对象销毁之后继续发送通知
[YANotificationCenter.defaultCenter postNotificationName:@"Noti_7" object:nil userInfo:@{@"key": @"value"}];

9. 多个observer监听同一个通知,所有observer的相关方法均得到调用
[YANotificationCenter.defaultCenter addObserver:a selector:@selector(print) name:@"Noti_8" object:nil];
[YANotificationCenter.defaultCenter addObserver:b selector:@selector(print) name:@"Noti_8" object:nil];
[YANotificationCenter.defaultCenter addObserver:c selector:@selector(print) name:@"Noti_8" object:nil];
[YANotificationCenter.defaultCenter postNotificationName:@"Noti_8" object:nil userInfo:@{@"key": @"value"}];
10. 如果notificationName为nil,object有值,则接收所有指定为object的通知
// 接收self发送的所有通知
[YANotificationCenter.defaultCenter addObserver:self selector:@selector(print1) name:nil object:self];
[YANotificationCenter.defaultCenter postNotificationName:@"Noti_9" object:self userInfo:@{@"key": @"value"}];
11. 如果notificationName为nil,且object也为nil,则接收系统内所有通知(也即YANotificationCenter发出的所有通知)
// 接收系统内所有通知,也即任意一个通知都会触发
[YANotificationCenter.defaultCenter addObserver:self selector:@selector(print1) name:nil object:nil];
12. 移除所有通知后,相关方法不再调用
[YANotificationCenter.defaultCenter addObserver:self selector:@selector(print1) name:@"Noti_10" object:nil];
// 移除所有通知后,再次发送通知没有效果
[YANotificationCenter.defaultCenter removeObserver:self];
[YANotificationCenter.defaultCenter postNotificationName:@"Noti_10" object:nil userInfo:@{@"key": @"value"}];

13. 移除指定通知后,相关方法不再调用
[YANotificationCenter.defaultCenter addObserver:self selector:@selector(print1) name:@"Noti_11" object:nil];
// 移除指定通知后,再次发送通知没有效果
[YANotificationCenter.defaultCenter removeObserver:self name:@"Noti_11" object:nil];
[YANotificationCenter.defaultCenter postNotificationName:@"Noti_11" object:nil userInfo:@{@"key": @"value"}];

实现文件

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#import "YANotificationCenter.h"

typedef NS_ENUM(NSUInteger, YANotificationSenderType) {
YANotificationSenderTypeObject = 0, // 指定对象
YANotificationSenderTypeObserver = 1, // 观察者
};

@interface YANotificationCenter()
// Recorder observer.
@property (nonatomic, strong) NSMutableDictionary *observerMap;
// Recorder all the selector.
@property (nonatomic, strong) NSMutableDictionary *selectorMap;
@end
@implementation YANotificationCenter
- (instancetype)init {
if (self = [super init]) {
_observerMap = [NSMutableDictionary dictionary];
_selectorMap = [NSMutableDictionary dictionary];
}
return self;
}

+ (YANotificationCenter *)defaultCenter {
static YANotificationCenter *center = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
center = [[self alloc] init];
});
return center;
}

- (void)addObserver:(id)observer selector:(SEL)aSelector name:(YANotificationName)aName object:(id)anObject {
if (!observer) return;
if (!aName) aName = (id)kCFNull;
NSMapTable *map = [self.observerMap objectForKey:aName];
if (!map) map = [NSMapTable weakToStrongObjectsMapTable];
if (anObject) {
NSHashTable *set = [map objectForKey:observer];
if (!set || ![set isKindOfClass:[NSHashTable class]]) set = [NSHashTable weakObjectsHashTable];
[set addObject:anObject];
[map setObject:set forKey:observer];
} else {
[map setObject:(id)kCFNull forKey:observer];
}
[self.observerMap setObject:map forKey:aName];
NSString *key = generateKey(observer, anObject, aName);
[self.selectorMap setObject:NSStringFromSelector(aSelector) forKey:key];
}

- (void)postNotificationName:(NSNotificationName)aName object:(id)anObject {
[self postNotificationName:aName object:anObject userInfo:nil];
}

- (void)postNotificationName:(YANotificationName)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo {
// When the notification name is nil.
{
NSMapTable *map = [self.observerMap objectForKey:(id)kCFNull];
id key = nil;
NSEnumerator *enumerator = map.keyEnumerator;
while (key = [enumerator nextObject]) {
NSHashTable *set = [map objectForKey:key];
NSArray *selectorList = nil;
if (set == (id)kCFNull) {
selectorList = self.selectorMap.allValues;
} else if ([set containsObject:anObject]) {
selectorList = selectorListForSender(self, anObject, YANotificationSenderTypeObject);
}
if (!selectorList) break;
[selectorList enumerateObjectsUsingBlock:^(NSString *selector, NSUInteger idx, BOOL * _Nonnull stop) {
SEL sel = NSSelectorFromString(selector);
((void (*)(id, SEL, id))[key methodForSelector:sel])(self, sel, aUserInfo);
}];
}
}

// Normal process.
NSMapTable *map = [self.observerMap objectForKey:aName];
id key = nil;
NSEnumerator *enumerator = map.keyEnumerator;
if (anObject) {
while (key = [enumerator nextObject]) {
NSHashTable *set = [map objectForKey:key];
if (set == (id)kCFNull || [set containsObject:anObject]) {
NSString *selector = [self.selectorMap objectForKey:generateKey(key, anObject, aName)];
if (!selector) return;
SEL sel = NSSelectorFromString(selector);
((void (*)(id, SEL, id))[key methodForSelector:sel])(self, sel, aUserInfo);
}
}
} else {
while (key = [enumerator nextObject]) {
NSString *selector = [self.selectorMap objectForKey:generateKey(key, anObject, aName)];
if (!selector) return;
SEL sel = NSSelectorFromString(selector);
((void (*)(id, SEL, id))[key methodForSelector:sel])(self, sel, aUserInfo);
}
}
}

- (void)removeObserver:(id)observer {
[self.observerMap.allValues enumerateObjectsUsingBlock:^(NSMapTable *map, NSUInteger idx, BOOL * _Nonnull stop) {
[map removeObjectForKey:observer];
}];
[self.selectorMap removeObjectsForKeys:selectorListForSender(self, observer, YANotificationSenderTypeObserver)];
}

- (void)removeObserver:(id)observer name:(YANotificationName)aName object:(id)anObject {
NSMapTable *map = [self.observerMap objectForKey:aName];
// Remove selector.
[self.selectorMap removeObjectForKey:generateKey(observer, anObject, aName)];
if (anObject) {
NSHashTable *set = [map objectForKey:observer];
[set removeObject:anObject];
if (set.count == 0) [map removeObjectForKey:observer];
} else {
[map removeObjectForKey:observer];
}
if (map.count == 0) [self.observerMap removeObjectForKey:aName];
}

static inline NSString *generateKey(id observer, id anObject, YANotificationName name) {
NSString *key = nil;
if (anObject) {
key = [NSString stringWithFormat:@"%p_%@_%p" ,anObject, name, observer];
} else {
key = [NSString stringWithFormat:@"%@_%p", name, observer];
}
return key;
}

static inline NSArray *selectorListForSender(YANotificationCenter *self, id object, YANotificationSenderType type) {
NSString *p = [NSString stringWithFormat:@"%p", object];
NSPredicate *predicate = nil;
if (type == YANotificationSenderTypeObject) {
predicate = [NSPredicate predicateWithFormat:@"SELF BEGINSWITH %@", p];
} else {
predicate = [NSPredicate predicateWithFormat:@"SELF ENDSWITH %@", p];
}
NSArray *keys = [self.selectorMap.allKeys filteredArrayUsingPredicate:predicate];
NSArray *result = [self.selectorMap objectsForKeys:keys notFoundMarker:(id)kCFNull];
return result.count == 0 ? nil : result;
}
@end

五、小结

  1. 同步通知内部实现是维护了两层映射:第一层name作为key,第二层object作为key,最终把observer保存在数组中。iOS9之后对observer是安全的弱引用。
  2. 异步通知内部维护了一个双向链表以满足“聚合(NSNotificationCoalescing)”选项,依赖Runloop的迭代以满足“时机(NSPostingStyle)”选项。
  3. 使用子线程通知需要依靠NSMachPort的通信。

日常开发中同步通知能满足绝大多数场景,异步通知的场景有限,苹果给出的几个例子还是很经典的。子线程通知也是常见的,比如子线程发通知主线程处理通知,但是我看大家都习惯直接利用GCD从子线程环境切换到主线程,再发出通知。

参考资料:
《Notification与多线程》
《NSNotification原理理解》
《深入思考NSNotification》
《深入理解iOS NSNotification》)