iOS 内存管理
内存管理
1.内存布局
alloc注册流程
alloc会先调用_objc_rootAlloc()函数,_objc_rootAlloc()中会调用callAlloc函数,然后会调用_objc_rootAllocWithZone(),最终会执行到_class_createInstanceFromZone()中,主要的申请内存逻辑在这个函数中。
cls->instanceSize计算开辟内存空间大小
calloc开辟内存, 返回地址指针(obj)
obj->initInstanceIsa 类与isa指针关联
2.内存管理方案
TaggedPointer
-
从64bit开始,iOS引入了Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象存储
-
在没有使用Tagged Pointer之前,NSNumber等对象需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值
-
使用Tagged Pointer之后,NSNumber指针里面存储的数据变成了:Tag + Data,也就是将数据直接存储在了指针中,Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要malloc和free。
-
在内存读取上有着3倍的效率,创建时比以前快106倍。不但减少了64位机器下程序的内存占用,还提高了运行效率。完美地解决了小内存对象在存储和访问效率上的问题。
-
这是一个特别的指针,不指向任何一个地址
-
当指针不够存储数据时,才会使用动态分配内存的方式来存储数据
Nonpointer_Isa
nonpointer 标记是否是nonpointerisa 如果不是就单纯的指向类 如果是就还有下边的一些信息 0 是 纯的isa。1 是nonpointerisa
has_assoc 是否有关联对象
has_cxx_dtor 判断是否有c++或者Objc的析构器。如果没有可以快速释放
shiftcls 开启指针优化 存储类指针
magic 用于调试判断当前对象时真的对象还是没有初始化的空间
weakly_referenced 标记当前对象是否被指向或者曾经指向一个弱引用对象 如果没有可以尽快释放
deallocating 是否正在释放
has_sidetable_rc 标记当前有没有sidetable 如果没有可以更快的释放
extra_rc 表示当前对象的引用计数 如果小于10 就存储在这里 如果大于10 情况变得复杂 要往sideTable中放
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
uintptr_t nonpointer : 1; // 0:普通指针,1:优化过,使用位域存储更多信息
uintptr_t has_assoc : 1; // 对象是否含有或曾经含有关联引用
uintptr_t has_cxx_dtor : 1; // 表示是否有C++析构函数或OC的dealloc
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; // 是否需要使用 sidetable 来存储引用计数
uintptr_t extra_rc : 19; // 引用计数能够用 19 个二进制位存储时,直接存储在这里
};
#endif
};
散列表:引用计数表与弱引用表
为什么不是一个table而是多个组成的tables? 成千上万对象引用计数都在操作同一张表 存在效率问题
解决这个问题 引入一个分离锁 多个表操作
怎么实现快速分流?
side tables的本质是一张Hash表
key(对象指针) -(hash函数运算)> value(side tables)
提高效率
3.数据结构
spinlock_t 是忙等的锁
适应于轻量访问
循环等待询问,不释放当前资源
引用计数 hash表 为了提高查找效率 插入跟查找通过hash函数获得的
weakly_referenced :对象是否有弱引用 0没有1有
Deallocating:当前对象是否正在alloc
Rc :对象实际引用计数值
弱引用表:
ARC:有编译器和runtime共同协作完成引用计数
引用计数:
两次hash表查询
+1操作
4.dealloc实现:
object_dispose()实现
clearDeallocating() 实现
-
dealloc底层调用_objc_rootDealloc()
-
_objc_rootDealloc()调用objc_object::rootDealloc()
-
objc_object::rootDealloc()调用object_dispose()
-
object_dispose()进行了free(obj)释放对象,同时调用objc_destructInstance()
-
在objc_destructInstance()函数中判断是否有析构函数和关联引用,如果有,就要移除,最后调用clearDeallocating()
-
在clearDeallocating()中进行引用计数refcnt的清除和weak指针的移除,并调用weak_clear_no_lock()(这个weak指针移除具体步骤在下面的weak指针清除的时候进行详细分析。)
5.weak弱指针存储的底层结构
-
Runtime全局维护了一个全局映射表StripedMap,根据对象的地址能够获取对应的散列表SideTable(注意!!!也有可能是多个对象共用一个散列表),散列表SideTable之中包含有weak表weak_table_t,weak_table_t中根据对象的地址能够查到该对象对应的weak_entry_t实体,weak_entry_t用来管理对象的所有的weak指针,weak指针存储在weak_referrer_t中。
-
当我们在用__weak修饰对象的时候,运行时Runtime会在底层调用objc_initWeak()方法
-
objc_initWeak()方法会调用storeWeak();
-
storeWeak()这个函数会先判断对象是否初始化,如果未初始化,则进行对象初始化,然后创建对应的SideTable;如果对象已经有SideTable,那么判断weak指针是否需要更新,更新操作就是删除对应location位置的weak_entry_t对象,创建新的weak_entry_t,然后插入到weak表weak_table_t中。
weak指针移除原理
1、移除时机:调用对象的dealloc方法时,中间会调用clearDeallocating,其中会调用weak_clear_no_lock对weak指针进行移除。
2、移除原理:weak_clear_no_lock底层会获取weak表weak_table_t中的实体weak_entry_t,然后拿到其中的weak_referrer_t,拿到weak_referrer_t之后,遍历并将其中的所有weak指针置为nil,最后把这个weak_entry_t从weak_table_t中移除。
3、weak指针本质:从源码中可以看出weak指针的类型为是objc_object**,是对象的二维指针,就是指向对象地址的指针。
注册函数weak_register_no_lock进行分析:如果一个对象正在进行dealloc的时候,进行weak指针的更新操作,就会直接crash!并报错Cannot form weak reference to instance (%p) of " "class %s. It is possible that this object was " "over-released, or is in the process of deallocation.
注册时这么判断作用就是:在对象正在释放的过程中,或者对象已经释放后,是不允许使用weak来引用实例变量的。这样就是为了防止野指针的出现。
小插曲:
最怕你一生碌碌无为 还安慰自己平凡可贵