【iOS】通知原理

我们可以通过看通知的实现机制来了解通知中心是怎么实现对观察者的引用的。由于苹果对Foundation源码是不开源的,我们具体就参考一下GNUStep的源码实现。GNUStep的源码地址为:GNUStep源码GitHub下载地址, 具体源码可以进行查看。

通知的主要流程

  • 通知全局对象是一个名为NCTbl的结构体,里头有三个重要的成员变量,分别是是两张GSIMapTable表:namednameless,及单链表wildcard
    • named是存放传入了通知名称的通知的hash表。
    • nameless是存放没有传入通知名称但是传入了消息发送者object的通知的hash表。
    • wildcard是存既没有传入通知名称,也没有传入消息发送者的通知的链表。
  • 我们每次注册一个通知的时候,所注册的那个通知就会按照这三种类型来对号入座放入相应的NCTbl的结构体中的GSIMapTablewildcard
  • 接着我们每次发送通知(发送消息)的时候,就是先创建存储所有匹配通知的数组GSIArray,按照以下流程将符合条件的通知添加到数组GSIArray中。
    1. 获取所有wildcard中符合条件的通知并添加到数组GSIArray
    2. nameless表中查找符合条件的通知并添加到数组GSIArray
    3. named表中查找符合条件的通知并添加到数组GSIArray
  • 最后待所有符合条件的通知都添加好之后,就遍历整个GSIArray数组并依次调用performSelector:withObject处理通知消息发送。

通知原理

数据结构

_GSIMapTable映射表数据结构图如下:

相关数据结构:

  • _GSIMapTable映射表包含了nodeChunksbuketsbuketCountchunkCount
  • nodeChunksnodeChunks 是一个指向 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表:namednameless,及单链表wildcard

  • named,保存着传入通知名称的通知hash
  • nameless,保存没有传入通知名称但传入了消息发送者objecthash
  • wildcard,保存既没有通知名称又没有传入object的通知的单链表

保存含有通知名称的通知表named需要注册object对象,因此该表结构体通过传入的name作为key。而它的value也是一个GSIMapTable表,这个表用于存储对应的object对象的observer对象;

对没有传入通知名称只传入object对象的通知表nameless而言,只需要保存objectobserver的对应关系,因此object作为keyobserver作为value

具体的添加观察者的核心函数(block形式只是该函数的包装)大致代码如下:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值