大家都知道内存管理是通过引用计数管理对象的生命周期。
那isa
和引用计数又有什么关系呢?先留着疑问。
isa指针
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
通过注释和代码不难发现,我们创建的一个对象或实例其实就是一个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(哈希表,key对象地址,value 是 SideTable)
|
|---SideTable
| |
| |---slock(自旋锁)
| |
| |---refcnts(引用计数哈希表, key 是对象地址,value是引用计数)
| |
| |---weak_table(弱引用哈希表,key是对象地址,value 是 entry)
| |
| |---entry(也可以理解为是哈希表,存储一个对象的所有弱引用,key 是弱引用地址(id*), value 也是弱引用地址)
| |
| |
| |---entry
| | .
| | .
| | .
|
|
|---SideTable
| .
| .
| .
SideTables
可以理解为一个全局的hash
数组,里面存储了SideTable
类型的数据,其长度为64。
SideTable
的RefCountMap
里面就存储着引用计数。
其中weak_table
就是weak
的具体实现。weak
表其实是一个hash
(哈希)表,Key
是对象的内存地址,Value
是指向该对象的所有弱引用的指针地址数组。
为什么SideTables
被设计成多个SideTable
组成的结构?
如果只有一个SideTable,那我们在内存中分配的所有对象的引用计数或者弱引用都放在这个SideTable中,那我们对对象的引用计数进行操作时,为了多线程安全就要加锁,就存在效率问题。
系统为了解决这个问题,就引入 “分离锁” 技术方案,提高访问效率。把对象的引用计数表分拆多个部分,对每个部分分别加锁,那么当所属不同部分的对象进行引用操作的时候,在多线程下就可以并发操作。所以,使用多个SideTable组成SideTables()结构。
arm64之后
苹果为了进一步提高性能,优化了isa
结构体指针。
struct objc_object {
private:
isa_t isa;
};
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if SUPPORT_PACKED_ISA
// extra_rc must be the MSB-most field (so it matches carry/overflow flags)
// nonpointer must be the LSB (fixme or get rid of it)
// shiftcls must occupy the same bits that a real class pointer would
// bits + RC_ONE is equivalent to extra_rc + 1
// RC_HALF is the high bit of extra_rc (i.e. half of its range)
// future expansion:
// uintptr_t fast_rr : 1; // no r/r overrides
// uintptr_t lock : 2; // lock for atomic property, @synch
// uintptr_t extraBytes : 1; // allocated with extra bytes
# if __arm64__ // 在 __arm64__ 架构下
# define ISA_MASK 0x0000000ffffffff8ULL // 用来取出 Class、Meta-Class 对象的内存地址
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
uintptr_t nonpointer : 1; // 0:代表普通的指针,存储着 Class、Meta-Class 对象的内存地址
// 1:代表优化过,使用位域存储更多的信息
uintptr_t has_assoc : 1; // 是否有设置过关联对象,如果没有,释放时会更快
uintptr_t has_cxx_dtor : 1; // 是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快
uintptr_t shiftcls : 33; // 存储着 Class、Meta-Class 对象的内存地址信息
uintptr_t magic : 6; // 用于在调试时分辨对象是否未完成初始化
uintptr_t weakly_referenced : 1; // 是否有被弱引用指向过,如果没有,释放时会更快
uintptr_t deallocating : 1; // 对象是否正在释放
uintptr_t has_sidetable_rc : 1; // 如果为1,代表引用计数过大无法存储在 isa 中,那么超出的引用计数会存储在一个叫 SideTable 结构体的 RefCountMap(引用计数表)散列表中
uintptr_t extra_rc : 19; // 里面存储的值是对象本身之外的引用计数的数量,retainCount - 1
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
...... // 在 __x86_64__ 架构下
};
如果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
中。