记录一些阅读在weak table源码中出现过的sidetable源码的分析与记录,顺带阅读retain与release的实现逻辑。
SideTables
在阅读关于弱引用表weak table相关源码时,我们大概了解了一些关于SideTable的相关知识,知道了他是一个散列表,也就是Key-Value格式存储的map。通过在源码中搜索SideTable,可以发现他的更多细节。
首先明确一点,SideTables与SideTable是两个不同的数据结构,我们先来看看SideTables
在源码中搜索SideTables
,可以找到它的相关定义。
可以看到,SideTables
是一个StripedMap<SideTable>
类型的全局静态变量,实际上是调用了SideTablesMap.get()
函数,该函数定义在objc::ExplicitInit
类中。
接下来看看StripedMap
是一个什么结构。
通过注释我们可以知道,StripedMap
是一个泛型结构,他是一个void* -> T
类型的Map,也就是说它是一个key为void*,value为T
的哈希表,即我们的SideTables
是一个哈希表。
在上图的红色框部分,我们可以看到代码定义了StripeCount
这个变量,该变量在IPhone真机上为8,在其他平台上为64.在接下来的代码中我们可以看到该变量的作用。
可以看到,StripeCount
变量被用来创建一个类型为PaddedT
的数组。在这里我们可以知道,SideTables实际上是一个存储了类型为T的变量的哈希表,它通过indexForPointer
函数计算出指针的哈希值,可以看到哈希算法为((addr >> 4) ^ (addr >> 9)) % StripeCount;
又从之前的static StripedMap<SideTable>& SideTables()
可以知道,SideTables
即为一个存储了SideTable
的哈希表,该表在IPhone真机上长度为8,也就是一共有8个SideTable,在其他平台上长度为64,即一共有64个SideTable。
接下来让我们来看看SideTable的结构。
SideTable
在源码中搜索SideTable
,可以找到SideTable
的结构定义。
在结构体中只有三个变量,分别是spinlock_t
类型的自旋锁,用于加锁防止多线程操作导致的数据竞赛问题;RefcountMap
类型的引用计数表,weak_table_t
类型的弱引用表,弱引用表在之前的文章中已分析过。
我们在这里先来回顾一下之前的弱引用表中的弱引用对象插入表的操作,来回顾一下weak table与sidetable的联系。
weak table与sidetable
可以看到,在弱引用对象的插入方法中,方法在一开始先定义了旧对象与新对象的SideTable变量,并通过&SideTable[obj]
方法来获取到对象对应的SideTable。而在下图中的弱引用对象实际的插入时,我们可以看到弱应用表是用sidetable->weak_table
来获得的。
也就是说,在Runtime说虽然说广义上是只存在了一个全局的弱引用表,但实际上弱引用表是不止一个的,每个对象根据对象的地址找到自己对应的SideTable,再通过SideTable找到自己对应的Weak Table,即在IPhone真机中共有最多8个SideTable和8个Weak table,在其他平台上共有最多64个SideTable和64个WeakTable,引用计数表同理。这些通过散列表存储的分散表一起组成了广义上的全局弱引用表/引用计数表。
Retain与Release
Retain
在上文中我们理清了SideTables、SideTable与弱引用表、引用计数表之间的关系,接着我们来尝试一下看看Retain
和Release
方法。
在MRC中,调用Retain
方法会使得对象的引用计数+1,而调用Release
方法会使得对象的引用计数-1,当引用计数为0时该对象就会被系统释放。
首先在源码中搜索retain
。
可以看到retain
方法实际只调用了_objc_rootRetain(self)
方法,通过self
将对象传入函数。
这里我们跳过中间的一个私有方法,直接来看最终的rootRetain()
函数。
可以看到,在rootRetain
函数中与SideTable相关的并且进行相关retain操作的代码在上图红框里,在这里分为了sidetable_tryRetain
与sidetable_retain
两个方法。
接着我们进到sidetable_retain
方法中看看它的具体实现。
sidetable_retain
的具体实现并不复杂,可以看到首先是检查了一下参数,接着通过**全局静态变量SideTables()**取到该对象对应的SideTable
,这里我们注意到,在前文已经提到SideTables是一个哈希表,所以这里直接使用SideTables()[this]
的语法就可以取到该对象对应的SideTable。
SideTable中包含了自旋锁、引用计数表、弱引用表三个属性,由于此处进行的是retain
操作,所以首先对表加锁,接着通过table.refcnts[this]
取出对象对应的引用计数。从这里可以看出引用计数表实际上也是一张哈希表。
接着就是判断后对该对象的引用计数➕1了,SIDE_TABLE_RC_ONE
实际上就是在宏定义的1,所以retain
操作会使得对象的引用计数加一,最后释放SideTable的自旋锁。
Release
接下来看看release
的相关实现,第一步依然是找出release
在代码中的具体实现。
可以看到在objc_object::rootRelease
函数里的关键代码就是sidetable_release()
函数,该函数负责了将对象的引用计数-1。objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
第一个参数从命名可以看到,该参数标示了对象在被release了之后是否要被释放。
可以看到在sidetable_release
函数中同样是通过全局静态变量SideTables获取到对象对应的SideTable,接着通过引用计数的判断来设置do_dealloc
变量的值,若不需要释放则对引用计数-1,若需要释放,则将对象的引用计数设为SIDE_TABLE_DEALLOCATING
状态并且在最后通过objc_msgSend
调用对象的dealloc
方法,自此release
方法执行完毕。
写在最后
经过对SideTables、SideTable的探索和学习,进一步加深了对之前weak table的理解。SideTable将引用计数表、弱引用表串联了起来,并且对内存管理中的retain
、release
也有了更深的理解。