通知,简单来说就是,一方使用name发送通知,持有与之相同name的另一方接收通知并进行处理(如果添加的观察者name为空,那么就会接收所有通知)。特点是基本上不用考虑其它影响因素,只需要使用同样的通知名称,监听该通知的对象(即观察者)再对通知做出反应即可。降低耦合度
// 添加观察者 这个方法一定要是@objc
// 可以发现 这个方法的name为可选值,可以为nil
NotificationCenter.default.addObserver(self, selector: #selector(test), name: NSNotification.Name.init("test"), object: nil)
// 发送通知
// name不是可选值,必须得有名字
NotificationCenter.default.post(name: NSNotification.Name.init("test"), object: nil, userInfo: nil)
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"test" object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:@"test" object:nil];
从使用代码中可以看出来,NSNotificationCenter是一个单例
GNUStep的源码
创建源码:
static NSNotificationCenter *default_center = nil;
+ (NSNotificationCenter*) defaultCenter
{
return default_center;
}
NSNotificationCenter
内部主要定义了两个表和一个链表,还有一个对象结构体
typedef struct NCTbl {
Observation *wildcard; /* Get ALL messages. */
GSIMapTable nameless; /* Get messages for any name. */
GSIMapTable named; /* Getting named messages only. */
} NCTable
typedef struct Obs {
id observer; /* Object to receive message. */
SEL selector; /* Method selector. */
struct Obs *next; /* Next item in linked list. */
int retained; /* Retain count for structure. */
struct NCTbl *link; /* Pointer back to chunk table */
} Observation;
还有一些宏定义,下面会用到
#define TABLE ((NCTable*)_table)
#define WILDCARD (TABLE->wildcard)
#define NAMELESS (TABLE->nameless)
#define NAMED (TABLE->named)
#define LOCKCOUNT (TABLE->lockCount)
named表是一个嵌套表,外层以 NotificationName 为key,在注册观察者时可以传入一个object参数,所以内层是以 object 为 key,Observation 为 value,又因为可以多个对象观察同一个对象,所以Observation 是一个链表,这一整个key-value表作为外层的value,大概图长这个样子:
PS:我们在实际开发中,经常会将object设为nil,这时系统会根据nil自动生成一个key,之后所有object为nil的观察者都会使用这个key,换句话说,这个key对应了所有object为nil的观察者
有了named表,nameless表应该不难想象,因为没有 NotificationName ,所以少了named表外层的key,将value单独拎出来就组成了named表。也就是,以object为key,Observation为value,结构图为:
named表是有 NotificationName 的表,nameless是没有 NotificationName 只有 object 的表,那么wildcard 当然就是兜底啦,既没有 NotificationName 也没有 object 的观察者就都存放在这里,也就是只有 Observation 这么一个值了,用链表连接在一起即可。在这里的观察者会接收到所有通知
使用方法addObserver:selector:name:object
根据GNUStep上的源码一步步分析:
- (void) addObserver: (id)observer
selector: (SEL)selector
name: (NSString*)name
object: (id)object
{
Observation *list;
Observation *o;
GSIMapTable m;
GSIMapNode n;
// observer为空时的报错
if (observer == nil)
[NSException raise: NSInvalidArgumentException
format: @"Nil observer passed to addObserver ..."];
// selector为空时的报
if (selector == 0)
[NSException raise: NSInvalidArgumentException
format: @"Null selector passed to addObserver ..."];
// observer不能响应selector时的报错
if ([observer respondsToSelector: selector] == NO)
{
[NSException raise: NSInvalidArgumentException
format: @"[%@-%@] Observer '%@' does not respond to selector '%@'",
NSStringFromClass([self class]), NSStringFromSelector(_cmd),
observer, NSStringFromSelector(selector)];
}
// 给表上锁
lockNCTable(TABLE);
// 建立一个新Observation
o = obsNew(TABLE, selector, observer);
// 如果有name
if (name) {
// 在named表中 以name为key寻找value
n = GSIMapNodeForKey(NAMED, (GSIMapKey)(id)name);
// named表中没有value
if (n == 0) {
// 新建一个表
m = mapNew(TABLE);
// 由于这是对给定名称的首次观察,因此我们对该名称进行了复制,以便在map中无法对其进行更改(来自GNUStep的注释)
name = [name copyWithZone: NSDefaultMallocZone()];
// 新建表作为name的value添加在named表中
GSIMapAddPair(NAMED, (GSIMapKey)(id)name, (GSIMapVal)(void*)m);
GS_CONSUMED(name)
} else {
// 取出对应的value
m = (GSIMapTable)n->value.ptr;
}
// 将observation添加到正确object的列表中
n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
// n是object的value
if (n == 0) {
o->next = ENDOBS;
// 将o作为object的value链表的头结点插入
GSIMapAddPair(m, (GSIMapKey)object, (GSIMapVal)o);
} else {
// 在链表尾部加入o
list = (Observation*)n->value.ptr;
o->next = list->next;
list->next = o;
}
// 这个else if 就是没用name有object的 Observation,对object进行的操作相同,
} else if (object) {
n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
if (n == 0) {
o->next = ENDOBS;
GSIMapAddPair(NAMELESS, (GSIMapKey)object, (GSIMapVal)o);
} else {
list = (Observation*)n->value.ptr;
o->next = list->next;
list->next = o;
}
} else {
// 既没有name又没有object,就加在WILDCARD链表中
o->next = WILDCARD;
WILDCARD = o;
}
// 解锁
unlockNCTable(TABLE);
}
总结
使用方法postNotification:
, postNotificationName:object:userInfo
或者postNotificationName:object:
,后者默认userInfo为nil
同样使用GNUStep源码进行分析
- (void) postNotification: (NSNotification*)notification {
if (notification == nil) {
[NSException raise: NSInvalidArgumentException
format: @"Tried to post a nil notification."];
}
[self _postAndRelease: RETAIN(notification)];
}
- (void) postNotificationName: (NSString*)name
object: (id)object {
[self postNotificationName: name object: object userInfo: nil];
}
- (void) postNotificationName: (NSString*)name
object: (id)object
userInfo: (NSDictionary*)info {
GSNotification *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:
方法。不同的是,postNotification:
方法外部直接传了一个NSNotification对象,其他两个方法都是内部进行了处理包装 成为了一个NSNotification对象
再看看_postAndRelease:
方法做了什么
- (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;
// name为空的报错
if (name == nil) {
RELEASE(notification);
[NSException raise: NSInvalidArgumentException
format: @"Tried to post a notification with no name."];
}
object = [notification object];
GSIArrayInitWithZoneAndStaticCapacity(a, _zone, 64, i);
lockNCTable(TABLE);
// 查找所有未指定name或object的观察者,加在a数组中
for (o = WILDCARD = purgeCollected(WILDCARD); o != ENDOBS; o = o->next)
{
GSIArrayAddItem(a, (GSIArrayItem)o);
}
// 查找与通知的object相同但是没有name的观察者,加在a数组中
if (object) {
n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
if (n != 0) {
o = purgeCollectedFromMapNode(NAMELESS, n);
while (o != ENDOBS) {
GSIArrayAddItem(a, (GSIArrayItem)o);
o = o->next;
}
}
}
// 查找name的观察者,但观察者的非零对象与通知的object不匹配时除外,加在a数组中
if (name) {
// 先匹配name
n = GSIMapNodeForKey(NAMED, (GSIMapKey)((id)name));
if (n) {
// 从匹配的name对应的value表中继续查找
m = (GSIMapTable)n->value.ptr;
} else {
m = 0;
}
if (m != 0) {
// 首先,查找与通知的object相同的观察者
n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
if (n != 0) {
o = purgeCollectedFromMapNode(m, n);
while (o != ENDOBS) {
GSIArrayAddItem(a, (GSIArrayItem)o);
o = o->next;
}
}
if (object != nil) {
// 接着是没有object的观察者
n = GSIMapNodeForSimpleKey(m, (GSIMapKey)nil);
if (n != 0) {
o = purgeCollectedFromMapNode(m, n);
while (o != ENDOBS) {
GSIArrayAddItem(a, (GSIArrayItem)o);
o = o->next;
}
}
}
}
}
unlockNCTable(TABLE);
// 发送所有通知
count = GSIArrayCount(a);
while (count-- > 0) {
o = GSIArrayItemAtIndex(a, count).ext;
if (o->next != 0) {
NS_DURING {
// 给observer发送selector,让其处理
[o->observer performSelector: o->selector
withObject: notification];
}
NS_HANDLER {
BOOL logged;
// 尝试将通知与异常一起报告,但是如果通知本身有问题,我们只记录异常。
NS_DURING
NSLog(@"Problem posting %@: %@", notification, localException);
logged = YES;
NS_HANDLER
logged = NO;
NS_ENDHANDLER
if (NO == logged)
{
NSLog(@"Problem posting notification: %@", localException);
}
}
NS_ENDHANDLER
}
}
lockNCTable(TABLE);
GSIArrayEmpty(a);
unlockNCTable(TABLE);
RELEASE(notification);
}
总结
注意 在iOS9之前需要手动调用removeObserver:
方法进行释放,如果在对象释放后没有及时移除通知,在对象释放之后再发送通知可能会造成野指针crash;但是在iOS9之后,通知中心持有的观察者由unsafe_unretained
引用变为weak
引用。即使不对观察者手动移除,持有的观察者的引用也会在观察者被回收后自动置空。但是通过addObserverForName:object: queue:usingBlock:
方法注册的观察者需要手动释放,因为通知中心持有的是它们的强引用。如果使用 Block 的方式进行添加的,一定要持有对象,在释放的时候,需要自己手动进行释放操作,不然会出现对象释放了,但是发送通知的时候,block还是会被调用,此时 bolck 中的 self 对象已经是 nil 了
GNUStep源码:
- (void) removeObserver: (id)observer {
if (observer == nil)
return;
[self removeObserver: observer name: nil object: nil];
}
- (void) removeObserver: (id)observer
name: (NSString*)name
object: (id)object {
if (name == nil && object == nil && observer == nil)
return;
lockNCTable(TABLE);
// name和object都为nil,就在wildcard链表里删除
if (name == nil && object == nil) {
WILDCARD = listPurge(WILDCARD, observer);
}
// name为空时
if (name == nil) {
GSIMapEnumerator_t e0;
GSIMapNode n0;
// 首先尝试删除为此object设置的所有命名项目
// 在named表中
e0 = GSIMapEnumeratorForMap(NAMED);
n0 = GSIMapEnumeratorNextNode(&e0);
while (n0 != 0) {
GSIMapTable m = (GSIMapTable)n0->value.ptr;
NSString *thisName = (NSString*)n0->key.obj;
n0 = GSIMapEnumeratorNextNode(&e0);
if (object == nil) {
// 清空named表
GSIMapEnumerator_t e1 = GSIMapEnumeratorForMap(m);
GSIMapNode n1 = GSIMapEnumeratorNextNode(&e1);
while (n1 != 0) {
GSIMapNode next = GSIMapEnumeratorNextNode(&e1);
purgeMapNode(m, n1, observer);
n1 = next;
}
} else {
// 以object为key找到对应链表,清空链表
GSIMapNode n1;
n1 = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
if (n1 != 0) {
purgeMapNode(m, n1, observer);
}
}
if (m->nodeCount == 0) {
mapFree(TABLE, m);
GSIMapRemoveKey(NAMED, (GSIMapKey)(id)thisName);
}
}
// 开始操作nameless表
if (object == nil) {
// 清空nameless表
e0 = GSIMapEnumeratorForMap(NAMELESS);
n0 = GSIMapEnumeratorNextNode(&e0);
while (n0 != 0) {
GSIMapNode next = GSIMapEnumeratorNextNode(&e0);
purgeMapNode(NAMELESS, n0, observer);
n0 = next;
}
} else {
// 找到对应的observer链表,清空
n0 = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
if (n0 != 0) {
purgeMapNode(NAMELESS, n0, observer);
}
}
} else {
GSIMapTable m;
GSIMapEnumerator_t e0;
GSIMapNode n0;
n0 = GSIMapNodeForKey(NAMED, (GSIMapKey)((id)name));
// 如果没有和这个name相同的key,直接返回
if (n0 == 0) {
unlockNCTable(TABLE);
return; /* Nothing to do. */
}
m = (GSIMapTable)n0->value.ptr;
if (object == nil) {
// 如果object为nil,就清空找到的table
e0 = GSIMapEnumeratorForMap(m);
n0 = GSIMapEnumeratorNextNode(&e0);
while (n0 != 0) {
GSIMapNode next = GSIMapEnumeratorNextNode(&e0);
purgeMapNode(m, n0, observer);
n0 = next;
}
} else {
// 清空object对应的链表
n0 = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
if (n0 != 0) {
purgeMapNode(m, n0, observer);
}
}
// 清除named表中的作为key的name
if (m->nodeCount == 0) {
mapFree(TABLE, m);
GSIMapRemoveKey(NAMED, (GSIMapKey)((id)name));
}
}
unlockNCTable(TABLE);
}
总结
GNUStep源码
NSNotificationCenter使用总结
底层初窥——NSNotificationCenter