我们可以通过看通知的实现机制来了解通知中心是怎么实现对观察者的引用的。由于苹果对Foundation源码是不开源的,我们具体就参考一下GNUStep的源码实现。GNUStep的源码地址为:GNUStep源码GitHub下载地址, 具体源码可以进行查看。
通知的主要流程
- 通知全局对象是一个名为
NCTbl
的结构体,里头有三个重要的成员变量,分别是是两张GSIMapTable
表:named
、nameless
,及单链表wildcard
。named
是存放传入了通知名称的通知的hash
表。nameless
是存放没有传入通知名称但是传入了消息发送者object
的通知的hash
表。wildcard
是存既没有传入通知名称,也没有传入消息发送者的通知的链表。
- 我们每次注册一个通知的时候,所注册的那个通知就会按照这三种类型来对号入座放入相应的
NCTbl
的结构体中的GSIMapTable
或wildcard
。 - 接着我们每次发送通知(发送消息)的时候,就是先创建存储所有匹配通知的数组
GSIArray
,按照以下流程将符合条件的通知添加到数组GSIArray
中。- 获取所有
wildcard
中符合条件的通知并添加到数组GSIArray
。 - 在
nameless
表中查找符合条件的通知并添加到数组GSIArray
。 - 在
named
表中查找符合条件的通知并添加到数组GSIArray
。
- 获取所有
- 最后待所有符合条件的通知都添加好之后,就遍历整个
GSIArray
数组并依次调用performSelector:withObject
处理通知消息发送。
通知原理
数据结构
_GSIMapTable
映射表数据结构图如下:
相关数据结构:
_GSIMapTable
映射表包含了nodeChunks
、bukets
、buketCount
、chunkCount
。nodeChunks
:nodeChunks
是一个指向GSIMapNode
指针数组的指针。它用于管理动态分配的内存块,这些内存块用于存储哈希表的节点(GSIMapNode
)。bukets
:记录单链表节点指针数组的各个链表的节点数量及链表首部地址。bucketCount
:记录了node
节点的数目。chunkCount
:记录单链表节点指针数组的数目。nodeCount
:哈希表中当前已使用的节点数量。
定义源代码:
typedef struct _GSIMapBucket GSIMapBucket_t;
typedef struct _GSIMapNode GSIMapNode_t;
typedef GSIMapBucket_t *GSIMapBucket;
typedef GSIMapNode_t *GSIMapNode;
typedef struct _GSIMapTable GSIMapTable_t;
typedef GSIMapTable_t *GSIMapTable;
struct _GSIMapNode {
GSIMapNode nextInBucket; /* Linked list of bucket. */
GSIMapKey key;
#if GSI_MAP_HAS_VALUE
GSIMapVal value;
#endif
};
struct _GSIMapBucket {
uintptr_t nodeCount; /* Number of nodes in bucket. */
GSIMapNode firstNode; /* The linked list of nodes. */
};
struct _GSIMapTable {
NSZone *zone;
uintptr_t nodeCount; /* Number of used nodes in map. */
uintptr_t bucketCount; /* Number of buckets in map. */
GSIMapBucket buckets; /* Array of buckets. */
GSIMapNode freeNodes; /* List of unused nodes. */
uintptr_t chunkCount; /* Number of chunks in array. */
GSIMapNode *nodeChunks; /* Chunks of allocated memory. */
uintptr_t increment;
#ifdef GSI_MAP_EXTRA
GSI_MAP_EXTRA extra;
#endif
};
具体的从映射表中添加/删除的代码如下:
GS_STATIC_INLINE GSIMapBucket
GSIMapPickBucket(unsigned hash, GSIMapBucket buckets, uintptr_t bucketCount)
{
return buckets + hash % bucketCount;
}
GS_STATIC_INLINE GSIMapBucket
GSIMapBucketForKey(GSIMapTable map, GSIMapKey key)
{
return GSIMapPickBucket(GSI_MAP_HASH(map, key),
map->buckets, map->bucketCount);
}
GS_STATIC_INLINE void
GSIMapLinkNodeIntoBucket(GSIMapBucket bucket, GSIMapNode node)
{
node->nextInBucket = bucket->firstNode;
bucket->firstNode = node;
}
GS_STATIC_INLINE void
GSIMapUnlinkNodeFromBucket(GSIMapBucket bucket, GSIMapNode node)
{
if (node == bucket->firstNode)
{
bucket->firstNode = node->nextInBucket;
}
else
{
GSIMapNode tmp = bucket->firstNode;
while (tmp->nextInBucket != node)
{
tmp = tmp->nextInBucket;
}
tmp->nextInBucket = node->nextInBucket;
}
node->nextInBucket = 0;
}
其实就是一个hash表结构,可以以数组的形式取到每个单链表首元素,再利用链表结构增删。
通知全局对象表结构如下:
typedef struct NCTbl {
Observation *wildcard; /* Get ALL messages*///获取所有消息
GSIMapTable nameless; /* Get messages for any name.*///获取任何名称的消息
GSIMapTable named; /* Getting named messages only.*///仅获取命名消息
unsigned lockCount; /* Count recursive operations. *///递归运算计数
NSRecursiveLock *_lock; /* Lock out other threads. *///锁定其他线程
Observation *freeList;
Observation **chunks;
unsigned numChunks;
GSIMapTable cache[CACHESIZE];
unsigned short chunkIndex;
unsigned short cacheIndex;
} NCTable;
其中数据结构中重要的是两张GSIMapTable
表:named
、nameless
,及单链表wildcard
named
,保存着传入通知名称的通知hash
表nameless
,保存没有传入通知名称但传入了消息发送者object
的hash
表wildcard
,保存既没有通知名称又没有传入object
的通知的单链表
保存含有通知名称的通知表named
需要注册object
对象,因此该表结构体通过传入的name
作为key
。而它的value
也是一个GSIMapTable
表,这个表用于存储对应的object
对象的observer
对象;
对没有传入通知名称只传入object
对象的通知表nameless
而言,只需要保存object
与observer
的对应关系,因此object
作为key
用observer
作为value
。