iOS面试 -- 内存管理

来源:爱玩游戏的iOS菜鸟

内存布局

iOS面试 -- 内存管理_第1张图片

iOS程序下内存布局

不同内存布局区域的含义
stack(栈):方法调用
heap(堆):通过alloc等分配的对象
bss:未初始化的全局变量
data:已初始化的全局变量
text:程序代码

内存管理方案

  • taggedPointer
  • NONPointer_ISA
  • 散列表(很复杂的数据结构,引用计数表、弱引用表)
散列表
  • SideTables()(非嵌入式系统中包含64个SideTable),实际是一个哈希表,通过对象的指针找到对应的引用计数表或弱引用表,在哪一个SideTable中

  • SideTable结构
    包含自旋锁 引用计数表 弱引用表

  • 为什么不是一个SideTable?
    存在效率问题,如果多个对象在对同一张表进行引用计数时,就会等待前一个对象操作结束才能操作。引用分离锁的方案,可以提高效率

  • 如果实现快速分流?(哈希查找的过程)
    根据对象的地址,通过一个均匀散列函数的计算就可以得到数组下标索引值

散列表中数据结构

  • 自旋锁(Spinlock_t
    是一种忙等的锁(当前锁已被其他线程获取,就会不断的探测这个锁是否被释放)
    适用于轻量访问

  • 引用计数表(RefcountMap
    ptr ——> DisguisedPtr(obj) ——>size_t
    提高查找效率,插入和获取都是通过同一个哈希算法,避免了数组遍历

  • 弱引用表(weal_table_t
    ptr ——> Hash函数——>value

ARC&&MRC

  1. MRC 手动引用计数
    • alloc
    • retain
    • release
    • retainCount
    • autorelease
    • dealloc

2.ARC 自动引用计数

  • ARC是LLVM和Runtime协作的结果
  • ARC禁止手动调用retain、release、retainCount、dealloc
  • ARC中新增weak、strong属性关键字

引用计数

  • alloc
    经过一系列调用,最终调用的C函数malloc,此时并没有设置引用计数为1(但是通过retainCount得知是1,在后面会讲到)

  • retain
    经过两次Hash查找,找到对应的引用计数值,然后进行+1的操作

  • release
    经过两次Hash查找,找到对应的引用计数值,然后进行-1的操作

  • retainCount
    经过两次Hash查找,找到对应的引用计数值,然后与1相加(因此刚alloc的对象,在对应的引用计数表中实际是没有这个映射的)

  • dealloc

iOS面试 -- 内存管理_第2张图片
dealloc实现流程

判断时候可以释放的条件(五个条件缺一不可)
*   没有使用nonpointer_isa
*   没有weak指针指向
*   没有有关联对象
*   没有使用ARC或者涉及C++
*   当前对象的引用计数没有通过SideTable中的引用计数表来存储的
iOS面试 -- 内存管理_第3张图片

object_dospose()函数内部实现分析

iOS面试 -- 内存管理_第4张图片

clearDeallocating()内部实现

iOS面试 -- 内存管理_第5张图片

clearDeallocating()内部实现

弱引用

iOS面试 -- 内存管理_第6张图片

weak变量的添加过程

如何添加weak变量的?
对象指针在经过编译器的编译之后调用objc_initweak(),然后storeweak()方法,经过一系列的函数调用,最终在weak_register_no_lock()进行弱引用变量的添加,通过hash算法位置查找,如果已经存在当前对象对应的弱引用数组,则直接加进去,如果没有则创建新个新的弱引用数组,存放新的weak指针

iOS面试 -- 内存管理_第7张图片

系统如何实现将废弃的weak指针置为nil

系统如何实现将废弃的weak指针置为nil?
当对象被dealloc后废弃之后,会调用弱引用清除的相关函数。然后在函数实现中,根据当前对象指针,查找弱引用表,把当前对象对应的弱引用都拿出来,然后遍历所有的弱引用指针置为nil

自动释放池

AutoreleasePool的实现原理是怎么样的?

AutoreleasePool是以栈为结点,通过双向链表的形式组合而成的数据结构。编译器会将@autoreleasepool{}改写,如下 图。实际objc_autoreleasePoolPop函数在内部做了pop操作,批量将autoreleasepool中的所有的对象都会做一次release操作

iOS面试 -- 内存管理_第8张图片

编译器改写@autoreleasepool{}

下面对上面的主要函数进行一个简单的说明

AutoreleasePool的结构
  • 是以栈为结点通过双向链表的形式组合而成
  • 是和线程一一对应的

什么是双向链表?

iOS面试 -- 内存管理_第9张图片

双向 链表结构

[obj autorelease]的实现(对象加入自动释放池)

先判断当前next指针是否指向栈顶,如果不是直接加入;如果是,则增加一个栈结点到链表上,在新的栈添加对象;移动next指针

AutoreleasePoolPage::push实现流程(释放池多层嵌套)
  • 插入哨兵对象

    iOS面试 -- 内存管理_第10张图片

    AutoreleasePoolPage::push

AutoreleasePoolPage::pop实现流程(与push相反)
  • 根据传入的哨兵对象找到对应的位置
  • 给上次push操作之后添加的对象依次添加release消息
  • 回退next指针到正确的位置
AutoreleasePool为何可以嵌套使用?

多次插入哨兵对象,也就是对一个新的releasePool的创建,如果当前栈没有满,则不需要创建新的page,如果满了,新增一个栈节点

下面这个图中,array对象在什么时候释放呢?
iOS面试 -- 内存管理_第11张图片

答:在档次runloop将要结束的时候调用AutoreleasePoolPage:pop(),对array对象执行release操作

AutoreleasePool的使用场景?

在for循环中,alloc图片数据等内存消耗较大的场景手动插入autoreleasePool,每一次for循环都进行一次内存的释放,降低内存消耗

循环引用

  • 自循环引用
  • 相互循环引用
  • 多循环引用
iOS面试 -- 内存管理_第12张图片
iOS面试 -- 内存管理_第13张图片
iOS面试 -- 内存管理_第14张图片
常见的循环引用以及破除方法:
  • 代理(delegate)
  • block
  • NSTimer
  • 大环引用
如何破除循环引用?
  • 避免产生循环引用
  • 在合适的时机手动断环
具体解决方案有哪些?
  • __weak
  • __block
  • __unsafe_unretained(与weak等效)
__block在ARC和MRC条件下的区别
  • MRC下,__block修饰对象不会增加其引用计数,避免了循环引用
  • ARC下,__block修饰对象会被强引用,无法避免,需手动破环
__unsafe_unretained破解
  • 修饰对象不会增加其引用计数,避免了循环引用
  • 如果找修饰对象在某一事迹被释放,产生悬空指针
循环引用的示例?(平时开发时是否有遇到循环引用,又是怎么解决的?)
  1. Block使用示例(在后面block讲解时)
  2. NSTimerd的循环引用问题

作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的点击加入群聊iOS交流群:789143298 进群密码123,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!

iOS面试 -- 内存管理_第15张图片

你可能感兴趣的:(iOS面试 -- 内存管理)