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来引用实例变量的。这样就是为了防止野指针的出现。
 
小插曲:
 
 
posted @ 2020-09-24 10:46  FakeCoder  阅读(2289)  评论(0编辑  收藏  举报