iOS面试之内存管理
原文地址https://www.jishudog.com/8744/html
推荐👇:
推荐作者:iOS的火影乱斗
内存布局
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
- 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交流圈:891 488 181 不管你是大牛还是小白都欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!
判断时候可以释放的条件(五个条件缺一不可)
- 没有使用nonpointer_isa
- 没有weak指针指向
- 没有有关联对象
- 没有使用ARC或者涉及C++
- 当前对象的引用计数没有通过SideTable中的引用计数表来存储的
[图片上传失败...(image-584a89-1608785619322)]
object_dospose()函数内部实现分析
clearDeallocating()内部实现
弱引用
weak变量的添加过程
如何添加weak变量的?
对象指针在经过编译器的编译之后调用objc_initweak(),然后storeweak()方法,经过一系列的函数调用,最终在weak_register_no_lock()进行弱引用变量的添加,通过hash算法位置查找,如果已经存在当前对象对应的弱引用数组,则直接加进去,如果没有则创建新个新的弱引用数组,存放新的weak指针
系统如何实现将废弃的weak指针置为nil
系统如何实现将废弃的weak指针置为nil?
当对象被dealloc后废弃之后,会调用弱引用清除的相关函数。然后在函数实现中,根据当前对象指针,查找弱引用表,把当前对象对应的弱引用都拿出来,然后遍历所有的弱引用指针置为nil
自动释放池
AutoreleasePool的实现原理是怎么样的?
AutoreleasePool是以栈为结点,通过双向链表的形式组合而成的数据结构。编译器会将@autoreleasepool{}改写,如下 图。实际objc_autoreleasePoolPop函数在内部做了pop操作,批量将autoreleasepool中的所有的对象都会做一次release操作
编译器改写@autoreleasepool{}
下面对上面的主要函数进行一个简单的说明
AutoreleasePool的结构
- 是以栈为结点通过双向链表的形式组合而成
- 是和线程一一对应的
什么是双向链表?
双向 链表结构
[obj autorelease]的实现(对象加入自动释放池)
先判断当前next指针是否指向栈顶,如果不是直接加入;如果是,则增加一个栈结点到链表上,在新的栈添加对象;移动next指针
AutoreleasePoolPage::push实现流程(释放池多层嵌套)
-
插入哨兵对象
AutoreleasePoolPage::pop实现流程(与push相反)
- 根据传入的哨兵对象找到对应的位置
- 给上次push操作之后添加的对象依次添加release消息
- 回退next指针到正确的位置
AutoreleasePool为何可以嵌套使用?
多次插入哨兵对象,也就是对一个新的releasePool的创建,如果当前栈没有满,则不需要创建新的page,如果满了,新增一个栈节点
下面这个图中,array对象在什么时候释放呢?
答:在档次runloop将要结束的时候调用AutoreleasePoolPage:pop(),对array对象执行release操作
AutoreleasePool的使用场景?
在for循环中,alloc图片数据等内存消耗较大的场景手动插入autoreleasePool,每一次for循环都进行一次内存的释放,降低内存消耗
循环引用
- 自循环引用
- 相互循环引用
- 多循环引用
自循环引用 对自身强持有
相互循环引用
多循环引用
常见的循环引用以及破除方法:
- 代理(delegate)
- block
- NSTimer
- 大环引用
如何破除循环引用?
- 避免产生循环引用
- 在合适的时机手动断环
具体解决方案有哪些?
- __weak
- __block
- __unsafe_unretained(与weak等效)
__block在ARC和MRC条件下的区别
- MRC下,__block修饰对象不会增加其引用计数,避免了循环引用
- ARC下,__block修饰对象会被强引用,无法避免,需手动破环
__unsafe_unretained破解
- 修饰对象不会增加其引用计数,避免了循环引用
- 如果找修饰对象在某一事迹被释放,产生悬空指针
循环引用的示例?(平时开发时是否有遇到循环引用,又是怎么解决的?)
- Block使用示例(在后面block讲解时)
- NSTimerd的循环引用问题
文章到这里就结束了,你也可以私信我及时获取面试资料。如果你有什么意见和建议欢迎给我留言。