iOS内存管理引用计数示例分析
山海飞鸟 人气:0内存管理机制
目前流行的内存管理机制主要有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
; - ② 第二次根据当前对象的内存地址,经过哈希查找从
SideTable
中的refcnts
中取出它的引用计数表。
使用多个SideTable
+分离锁技术方案是为了保证线程安全的同时兼顾访问效率
加载全部内容