大家都知道内存管理是通过引用计数管理对象的生命周期。
那isa和引用计数又有什么关系呢?先留着疑问。
###isa指针
通过注释和代码不难发现,我们创建的一个对象或实例其实就是一个struct objc_object结构体。
这个结构体只有一个成员变量,这是一个Class类型的变量isa,也是一个结构体指针,那这个指针又指向什么呢?
面向对象中每一个对象都必须依赖一个类来创建,因此对象的isa指针就指向对象所属的类根据这个类模板能够创建出实例变量、实例方法等。
结合这张图片,就很好理解了。类方法的实现又是如何查找并且调用的呢?这时,就需要引入元类来保证无论是类还是对象都能通过相同的机制查找方法的实现。整个过程被设计成了一个闭环。
总结一下就是,isa指针是用来维护对象和类之间的关系,并确保对象和类能够通过isa指针找到对应的方法,实例变量,属性,协议等。在arm64架构之前,isa就是一个普通的指针,直接指向objc_class,存储着Class、Meta-Class对象的内存地址。instance对象的isa指向class对象,class对象的isa指向meta-class对象;
###引用计数
####arm64之前
在arm64架构之前,引用计数都是存储在sidetable这个数据结构中。
SideTables可以理解为一个全局的hash数组,里面存储了SideTable类型的数据,其长度为64。
SideTable的RefCountMap里面就存储着引用计数。
其中weak_table就是weak的具体实现。weak表其实是一个hash(哈希)表,Key是对象的内存地址,Value是指向该对象的所有弱引用的指针地址数组。
为什么SideTables被设计成多个SideTable组成的结构?
如果只有一个SideTable,那我们在内存中分配的所有对象的引用计数或者弱引用都放在这个SideTable中,那我们对对象的引用计数进行操作时,为了多线程安全就要加锁,就存在效率问题。
系统为了解决这个问题,就引入 “分离锁” 技术方案,提高访问效率。把对象的引用计数表分拆多个部分,对每个部分分别加锁,那么当所属不同部分的对象进行引用操作的时候,在多线程下就可以并发操作。所以,使用多个SideTable组成SideTables()结构。
####arm64之后
苹果为了进一步提高性能,优化了isa结构体指针。
如果isa非nonpointer,即 arm64 架构之前的isa指针。由于它只是一个普通的指针,存储着Class、Meta-Class对象的内存地址,所以它本身不能存储引用计数,所以以前对象的引用计数都存储在一个叫SideTable结构体的RefCountMap(引用计数表)散列表中。
如果isa是nonpointer,则它本身可以存储一些引用计数。从以上union isa_t的定义中我们可以得知,isa_t中存储了两个引用计数相关的东西:extra_rc和has_sidetable_rc。
extra_rc:里面存储的值是对象本身之外的引用计数的数量,这 19 位如果不够存储,has_sidetable_rc的值就会变为 1;
has_sidetable_rc:如果为 1,代表引用计数过大无法存储在isa中,那么超出的引用计数会存储SideTable的RefCountMap中。
retain:如果isa是nonpointer,就将isa中的extra_rc存储的引用计数进行 +1,如果溢出,就将extra_rc中RC_HALF(extra_rc满值的一半)个引用计数转移到sidetable中存储。
release:如果isa是nonpointer,就将isa中的extra_rc存储的引用计数进行 -1。如果下溢,即extra_rc中的引用计数已经为 0,判断has_sidetable_rc是否为true即是否有使用Sidetable存储。如果有的话就申请从Sidetable中申请RC_HALF个引用计数转移到extra_rc中存储,如果不足RC_HALF就有多少申请多少,然后将Sidetable中的引用计数值减去RC_HALF(或是小于RC_HALF的实际值),将实际申请到的引用计数值 -1 后存储到extra_rc中。如果extra_rc中引用计数为 0 且has_sidetable_rc为false或者Sidetable中的引用计数也为 0 了,那就dealloc对象。
所以,如果isa是nonpointer,则对象的引用计数存储在它的isa_t的extra_rc中以及SideTable的RefCountMap中。