现在盛行的内存办理机制主要有GC
和RC
两种。
-
GC
(Garbage Collection):垃圾收回机制,定期查找不再运用的目标,开释目标占用的内存。 -
RC
(Reference Counting):引证计数机制。采用引证计数来办理目标的内存,当需求持有一个目标时,使它的引证计数 +1;当不需求持有一个目标的时分,使它的引证计数 -1;当一个目标的引证计数为 0,该目标就会被毁掉。
Objective-C
支持三种内存办理机制:ARC
、MRC
和GC
,但Objective-C
的GC
机制有渠道局限性,仅限于MacOS
开发中,iOS
开发用的是RC
机制,从MRC
到现在的ARC
。
- 一个新创建的OC目标引证计数默许是1,当引证计数减为0,OC目标就会毁掉,开释其占用的内存空间
- 调用
retain
会让OC目标的引证计数+1,调用release
会让OC目标的引证计数-1 - 内存办理的经验总结
- 当调用
alloc
、new
、copy
、mutableCopy
办法返回了一个目标,在不需求这个目标时,要调用release
或者autorelease
来开释它 - 想拥有某个目标,就让它的引证计数+1;不想再拥有某个目标,就让它的引证计数-1
- 当调用
- 能够通过以下私有函数来查看自动开释池的状况
-
extern void _objc_autoreleasePoolPrint(void)
;
-
以上咱们对 “引证计数” 这一概念做了初步了解,Objective-C 中的 “目标” 通过引证计数功能来办理它的内存生命周期。那么,目标的引证计数是怎么存储的呢?它存储在哪个数据结构里?
首先,不得不提一下isa
。
isa
-
isa
指针用来保护 “目标” 和 “类” 之间的联系,并确保目标和类能够通过isa
指针找到对应的办法、实例变量、特点、协议等; -
在 arm64 架构之前,
isa
便是一个一般的指针,直接指向objc_class
,存储着Class
、Meta-Class
目标的内存地址。instance
目标的isa
指向class
目标,class
目标的isa
指向meta-class
目标; -
从 arm64 架构开端,对
isa
进行了优化,用nonpointer
表示,变成了一个共用体(union
)结构,还运用位域来存储更多的信息。将 64 位的内存数据分开来存储着很多的东西,其中的 33 位才是拿来存储class
、meta-class
目标的内存地址信息。要通过位运算将isa
的值& ISA_MASK
掩码,才能得到class
、meta-class
目标的内存地址。
// objc.h
struct objc_object {
Class isa; // 在 arm64 架构之前
};
// objc-private.h
struct objc_object {
private:
isa_t isa; // 在 arm64 架构开端
};
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
中。
所以,假如isa
是nonpointer
,则目标的引证计数存储在它的isa_t
的extra_rc
中以及SideTable
的RefCountMap
中。
SideTable
// NSObject.mm
struct SideTable {
spinlock_t slock; // 自旋锁
RefcountMap refcnts; // 引证计数表(散列表)
weak_table_t weak_table; // 弱引证表(散列表)
......
}
SideTable
存储在SideTables()
中,SideTables()
实质也是一个散列表,能够通过目标指针来获取它对应的(引证计数表或者弱引证表)在哪一个SideTable
中。在非嵌入式体系下,SideTables()
中有 64 个SideTable
。以下是SideTables()
的界说:
// NSObject.mm
static objc::ExplicitInit<StripedMap<SideTable>> SideTablesMap;
static StripedMap<SideTable>& SideTables() {
return SideTablesMap.get();
}
所以,查找目标的引证计数表需求通过两次哈希查找:
- ① 第一次依据当时目标的内存地址,通过哈希查找从
SideTables()
中取出它地点的SideTable
; - ② 第2次依据当时目标的内存地址,通过哈希查找从
SideTable
中的refcnts
中取出它的引证计数表。
运用多个SideTable
+别离锁
技能计划是为了确保线程安全的一起兼顾拜访功率