通知的主要流程
- 通知全局对象是一个名为
NCTbl的结构体,里头有三个重要的成员变量,分别是是两张GSIMapTable表:named、nameless,及单链表wildcard。
named是存放传入了通知名称的通知的hash表。
nameless是存放没有传入通知名称但是传入了消息发送者object的通知的hash表。
wildcard是存既没有传入通知名称,也没有传入消息发送者的通知的链表。
- 我们每次注册一个通知的时候,所注册的那个通知就会按照这三种类型来对号入座放入相应的
NCTbl的结构体中的GSIMapTable或wildcard。
- 接着我们每次发送通知(发送消息)的时候,就是先创建存储所有匹配通知的数组
GSIArray,按照以下流程将符合条件的通知添加到数组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;
GSIMapKey key;
#if GSI_MAP_HAS_VALUE
GSIMapVal value;
#endif
};
struct _GSIMapBucket {
uintptr_t nodeCount;
GSIMapNode firstNode;
};
struct _GSIMapTable {
NSZone *zone;
uintptr_t nodeCount;
uintptr_t bucketCount;
GSIMapBucket buckets;
GSIMapNode freeNodes;
uintptr_t chunkCount;
GSIMapNode *nodeChunks;
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;
GSIMapTable nameless;
GSIMapTable named;
unsigned lockCount;
NSRecursiveLock *_lock;
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。
具体的添加观察者的核心函数(block形式只是该函数的包装)大致代码如下: