美文网首页
iOS---内存管理(3)

iOS---内存管理(3)

作者: BabyNeedCare | 来源:发表于2021-12-07 23:10 被阅读0次

    数据结构

    *Spinlock_t
    *RefcountMap
    *weak_table_t

    Spinlock_t:

    *Spinlock_t是“忙等”的锁。(忙等:如果当前锁已被其他线程获取,那么当前线程会不断的探测锁是否被释放,如果释放,自己第一时间获取这个锁)
    *适用于轻量访问。

    RefcountMap

    引用计数表是一张Hash表,通过指针可以找到对应对象的引用计数,实际查找过程也是Hash查找。
    这个Hash查找的Hash算法,实际上是对传入对象的指针做一个伪装操作,然后去获取对应的引用计数
    之所以使用Hash查找,就是为了提高查找效率。查找效率的提高,源于存储一个对象的引用计数是通过这个函数来计算存储位置的,而获取的对象所代表的引用计数的值的时候,也是通过这个函数来计算我们应该获取的索引位置。
    所以插入和获取都是通过同一个函数来计算位置的,这就避免了循环遍历操作
    所以说使用Hash查找可以提高查找效率
    Q:引用计数表是通过什么实现的
    A:使用Hash表实现
    Q: 为什么引用计数表要使用Hash查找?
    A:提高查找效率,插入和获取都是通过同一个函数来计算位置的,这就避免了循环遍历操作


    c9640bebe36f056b8941b3e548fec12.png 9058da1a793e947f94abff7313d502d.png

    引用计数是用64位比特位来表示的,
    第一个二进制位代表是否有弱引用。
    第二个二进制位代表当前对象是否正在进行 deallocating
    剩余位数存储的就是实际的引用计数值,计算对象实际的引用计数值,需要向右偏移2位,因为后面这两位要把它去掉,才能取到真实的引用计数值

    weak_table_t

    弱引用表, 也是一张Hash表, 在Runtime 源码中,系统是通过weak_table_t来定义的
    对象指针作为Key,通过Hash函数,就可以计算出对应的弱引用对象的存储位置,或者说查找的位置
    weak_entry_t实际也是个结构体数组,这个结构体数组中存储的每一个对象实际就是弱引用指针,也就是代码中定义的__weak id obj, obj的内存地址,或者说指针,就存储到weak_entry_t的结构体数组当中。


    image.png

    MRC

    手动引用计数
    alloc: 分配对象的内存空间
    retain:使对象的引用计数+1
    release:使对象的引用计数-1
    retainCount:可以获取当前对象的引用计数值
    autorelease:如果调用了一个对象的autorelease方法, 那么当前这个对象会在autorelease pool结束的时候调用它的release操作,进行引用计数-1
    dealloc: 内存管理方法,在MRC的时候,需要调用 [super dealloc] 来释放父类的成员变量


    1646232366(1).png

    谨记:在MRC中特有的方法:retain,release, retainCount, autorelease,如果在ARC中调用会引起编译报错。

    ARC

    Q:什么是ARC
    A: ARC实际上是编译器自动为我们插入retain,release操作之外,还需要runtime功能进行支持,然后由编译器和runtime共同协作,才能组成ARC的全部功能


    1646233864(1).png

    Q: MRC和ARC有什么区别?
    A: MRC是手动管理内存,ARC是由编译器和runtime协作来进行自动引用计数的内存管理,同时MRC中可以调用一些引用计数相关的方法,而ARC中是不能调用的

    Q:weak变量为何在对象释放的时候自动置为nil ?

    引用计数管理

    实现原理分析:
    alloc:经过一系列调用,最终调用了C函数的calloc, 此时并没有引用计数+1
    retain:
    objc_680源码截取片段
    1.SideTable &table = SideTables()[this];
    SideTables可以看成是多个SideTable组成的Hash表,经过Hash函数的计算,可以快速的在SideTables中找到对应的SideTable
    2.size_t & refcntStorage = table.refcnts[This];
    在SideTable的结构当中,去获取引用计数map这样的一个成员变量,通过当前对象的指针,在这个SideTable的引用计数表中去获取当前对象的引用计数值。
    refcnts就是SideTable的一个成员变量。也是引用计数表
    这个查找过程又是一次Hash查找。所以相当于在进行retain操作的时候,是经历了2次Hash查找,最终查找到的结果是size_t
    3.refcntStorage += SIDE_TABLE_RC_ONE;

    Q: 在进行retain操作的时候,系统是怎样查找对应的引用计数的。
    A: 经过2次Hash查找来找到的对应的引用计数值,然后进行相应的+1操作

    release:
    1.SideTable &table = SideTables()[this];
    2.RefCountMap::iterator it = table.refcnts.find(this);
    根据当前对象指针访问table当中的引用计数表,去查找对应的引用计数表
    3.it->second -= SIDE_TABLE_RC_ONE
    查找到后,进行-1操作

    retainCount:
    1.SideTable &table = SideTables()[this];
    2.size_t refcnt_result = 1
    声明一个局部变量,指定值是1
    3.RefCountMap::iterator it = table.refcnts.find(this);
    通过当前对象,到引用计数表中去查找
    4.refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
    把结果做一个向右偏移的操作,再结合局部变量的it,进行+的操作,返回给调用方

    刚 alloc出来的对象,在引用计数表中,是没有这个对象相关联的映射的,那么这个值 it读出来的就是0,另外由于局部变量refcnt_result是1,那么只经过alloc调用的产生的对象,去调用retainCount, 就可以获取到它的值为1

    dealloc

    image.png

    1.首先调用 _objc_rootDealloc()这样的私有函数

    1. _objc_rootDealloc()又会调用rootDealloc(), 在函数内部判断,当前对象是否能直接释放,直接释放的判断条件,依据于上图右侧的列表
      1). nonpointer_isa,判断当前对象是是否使用了非指针型的isa
      2). 当前对象是否有weak指针指向它
      3). 当前对象是否有关联对象
      4). 当前对象的内部实现,是否有涉及到一些C++相关的内容,以及当前对象是否使用ARC来管理内存,如果使用ARC管理内存或者说当前对象涉及到了一些C++的内容,这个判断标志都是YES
      5). 表示当前对象的引用计数是否通过sideTable当中的引用计数表来维护的
      3.只有1) && 2) && 3) && 4) && 5)都不包含,才可以采用C函数释放,否则要调用 object_dispose()清除的函数

    object_dispose()实现

    1646666526(1).png

    objc_destructInstance()实现

    image.png

    1). 首先判断当前对象当中是否有C++相关的内容,或者当前对象采用的是ARC,如果有,会调用object_cxxDestruct(), 如果没有的话,会判断当前对象是否有关联对象,如果有关联对象,会在dealloc内部实现调用_object_remove_associations(), 对象相关关联的移除。
    2 ). 关联对象移除后,会调用clearDeallocating(), 结束dealloc调用流程
    Q:通过关联对象的技术,为一个类添加的一些实例变量,那么在对象的dealloc方法中,是否有必要对它的关联对象进行移除操作呢?
    A: 在系统的dealloc内部实现当中,会自动判断当前对象是否有关联对象,如果有的话呢,系统就帮助我们把相关的关联对象一并移除掉

    clearDeallocating()实现

    image.png

    1).调用sidetable_clearDeallocating()的函数
    2).调用weak_clear_no_lock()函数,将指向该对象的弱引用指针置为nil
    3).table.refcnts.erase(), 将当前对象在引用计数表当中的存储数据清除掉
    4).结束调用流程
    Q: 当对象dealloc或者废弃后,它的weak指针为何会自动置为nil
    A: 就是因为在dealloc内部实现当中,有做相关对象的弱引用指针自动置为nil的操作

    弱引用管理

    image.png

    Q: 一个weak变量,是怎样被添加到弱引用表当中的
    A: 一个被声明为__weak的对象指针,通过编译器的编译后,会调用相应的objc_initWeak()方法,经过一系列的调用栈,最终在weak_register_no_lock()中进行弱引用的添加,具体添加的位置是通过Hash算法来进行位置查找的。
    如果查找对应位置当中,已经有当前对象的弱引用数组,就把新的弱引用变量添加到数组当中,如果没有,重新创建一个弱引用数组,第0个地址添加上最新的weak指针,后面的都初始化为0或者nil
    添加weak变量


    image.png

    Q: 当一个对象被释放或废弃之后, weak变量是怎样处理的?
    A: 当一个对象被dealloc后,在dealloc内部实现中,会调用弱引用清除的相关函数,在函数内部实现当中,会根据当前对象指针查找弱引用表,把当前对象相对应的弱引用都拿出来,是一个数组,然后遍历数组当中所有的弱引用指针,分别置为nil


    image.png

    自动释放池

    以下代码,array是在什么时候释放的?


    image.png

    Q: AutoreleasePool的实现原理是什么?
    A:

    Q: AutoreleasePool为什么可以嵌套使用
    A:

    Q: 什么是自动释放池/自动释放池的实现结构是怎样的?
    A: 是以栈为结点通过双向链表的形式组合而成的

    image.png image.png image.png

    批量pop操作的意思是,在autoreleasePool花括号中的对象,都会被发送一次release消息。

    image.png image.png image.png image.png

    从图上可知, AutoreleasePool是跟线程一一对应的


    image.png
    image.png image.png image.png

    比如 next 指针指向一个位置,如果此位置产生了新的对象(因为调用了 autorelease),添加对象后, next指针就会移动到新的位置,再次添加对象,就可以添加到新的 next上。

    image.png image.png image.png

    循环引用

    image.png image.png image.png image.png image.png image.png image.png

    __weak和__unsafe_unretained(引用计数没加一) 一样


    image.png image.png image.png

    循环引用示例:

    NSTimer的循环引用问题。


    image.png

    NSTimer被分派后,会被当前线程的Runloop强引用,或者说NSTimer是在主线程当中创建的,就由主线程的Runloop持有NSTimer, 即使是使用weak来修饰,但是由于Runloop是常驻线程,会对NSTimer强引用,再通过NSTimer对对象的强引用,仍然对这个对象进行了强引用,因此,即使VC页面退出,VC与对象的指向移除了,滚动栏由于被Runloop间接的引用持有了,这个对象也不会被释放,此时就出现内存泄漏.

    NSTimer有重复定时器和非重复定时器的区分。
    一般会对NSTimer调用invalid方法,另外将NSTimer置为nil。把循环引用破除掉。

    Q:如果重复多次回调的计时器,不能对NSTimer做invalid和置nil的操作,此时应如何破除循环引用呢?
    A:引入中间对象,VC退出后,就释放了对广告栏的强引用,当下次定时器的回调回来的时候,中间对象去查看对象是否释放掉了,就判断中间对象持有的对象是否为nil,这种方案也是利用了对象被释放了,weak指针会自动置为nil的特点来解决这个问题,如果中间对象持有的广告栏对象被释放了,就可以在中间对象的回调方法中,对NSTimer无效,并且置为nil, 就可以实现Runloop对Runloop的强引用,以及NSTimer对中间对象的强引用。


    image.png image.png image.png image.png

    总结

    Q:什么是ARC?
    A:ARC是有LLVM编译器和Runtime共同协作来实现自动引用计数的管理。

    Q:为什么weak指针指向的对象在废弃后会被自动置为nil?
    A: 当对象被废弃后,dealloc的内部实现当中,会调用弱引用的方法,在清除弱引用的方法当中会通过Hash算法来查找被废弃对象在弱引用表中的位置,来提取它对应的弱引用指针的一个列表数组,进行for循环遍历,把每一个weak指针都置为nil。

    Q:苹果是如何实现AutoreleasePool的?
    A:AutoreleasePool是以栈为节点,由双向链表来合成的一个数据结构。

    Q:什么是循环引用? 你遇到过哪些循环引用,是怎么解决的?
    A:

    相关文章

      网友评论

          本文标题:iOS---内存管理(3)

          本文链接:https://www.haomeiwen.com/subject/amrzxrtx.html