iOS SideTables与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,可以找到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与弱引用表、引用计数表之间的关系,接着我们来尝试一下看看RetainRelease方法。

在MRC中,调用Retain方法会使得对象的引用计数+1,而调用Release方法会使得对象的引用计数-1,当引用计数为0时该对象就会被系统释放。

首先在源码中搜索retain

可以看到retain方法实际只调用了_objc_rootRetain(self)方法,通过self将对象传入函数。

这里我们跳过中间的一个私有方法,直接来看最终的rootRetain()函数。

可以看到,在rootRetain函数中与SideTable相关的并且进行相关retain操作的代码在上图红框里,在这里分为了sidetable_tryRetainsidetable_retain两个方法。

接着我们进到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在代码中的具体实现。

rootRelease

可以看到在objc_object::rootRelease函数里的关键代码就是sidetable_release()函数,该函数负责了将对象的引用计数-1。 objc_object::rootRelease(bool performDealloc, bool handleUnderflow)第一个参数从命名可以看到,该参数标示了对象在被release了之后是否要被释放。

sidetable_release

可以看到在sidetable_release函数中同样是通过全局静态变量SideTables获取到对象对应的SideTable,接着通过引用计数的判断来设置do_dealloc变量的值,若不需要释放则对引用计数-1,若需要释放,则将对象的引用计数设为SIDE_TABLE_DEALLOCATING状态并且在最后通过objc_msgSend调用对象的dealloc方法,自此release 方法执行完毕。

写在最后

经过对SideTables、SideTable的探索和学习,进一步加深了对之前weak table的理解。SideTable将引用计数表、弱引用表串联了起来,并且对内存管理中的retainrelease也有了更深的理解。


Tino Wu
more at tinowu.top

猜你喜欢

转载自juejin.im/post/7033296033740226574
ios