- 内存布局
- 内存管理方案
- 数据结构
- ARC & MRC
- 引用计数
- 弱引用
- 自动释放池
- 循环引用
一、内存布局

* stack 方法调用
* heap 通过alloc分配的对象
* bss 未初始化的全局变量等
* data 已初始化的全局变量等
* text 程序代码
二、内存管理方案
- TaggedPointer ---- 一些小对象,如 NSNumber
- NONPOINTER_ISA ---- 64位架构下的 ios 程序
- 散列表 ---- 复杂的数据结构,包括引用计数表和弱引用表
三、数据结构
1. NONPOINTER_ISA

- indexed -- 标志位,若为0:此 isa 指针为纯的 isa 指针,里面的内容代表了当前对象类对象的地址;若为1:为非指针型的 isa ,不仅存储了类对象的地址,而且还有内存管理的数据
- has_assoc -- 是否有关联对象;0-无;1-有;
- has_cxx_dtor -- 当前对象是否使用到 c++ 相关代码
- shiftcls -- 当前对象类对象的指针地址,共33位
- magic -- 对内存管理无影响
- weakly_referenced -- 此对象是否有弱引用指针
- deallocating -- 是否有 dealloc 操作
- has_sidetable_rc -- 当前引用计数是否达到上限,是否引用散列表计数
- extra_rc -- 额外引用计数(若超出则使用散列表存储)
2. 散列表
SideTables()结构 -- 实际为一个哈希表

SideTable结构

问题:为什么SideTable是好几张表,而不是一张表:
假如是一张表的话,系统的所有对象都会在一张表中,那么当对其进行操作的时候,那么必然需要加锁,当有大量相似操作的时候,效率会大大降低;多张表相当于多线程操作,可以提高效率
问题:怎样实现快速分流(怎样根据key找到SideTable位置):
SideTables本质是一张Hash表,通过哈希查找找到下表

例如:给定值是对象的内存地址,目标值是数组下标索引

不会遍历所有的表,所以会提高查找效率。
3.散列表的数据结构
- 自旋锁 --- spinlock_t
- 引用计数表 --- RefcountMap
- 弱引用表 --- weak_table_t
自旋锁
- 是“忙等”的锁。即若当前所被其他线程所获取,当前线程会不断探索这个锁是否被释放,若被释放会第一时间获取;
- 适用于轻量访问。例如对当前对象做加一减一操作;
引用计数表
- 是一个哈希表,为了查找提高查找效率,其中插入和获取都是通过同一个哈希算法来实现的,从而避免了 for 循环遍历。

size_t

实际是一个无符号的long型的值;第一位是是否有弱引用;第二位是是否有dealloc;其它位是引用计数位,所以在计算引用计数位的时候,需要向右偏移两位;
弱引用表
- 也是一张哈希表

四、MRC、ARC
MRC
手动引用计数进行对象的内存管理

其中标红的是MRC特有的方法
ARC
- ARC 是 LLVM(编译器) 和 Runtime 协作的结果
- ARC 中禁止手动调用 retain/release/retainCount/dealloc
- ARC 中新增 weak、strong 关键字
五、引用计数管理
实现原理分析
- alloc
- retain
- release
- retainCount
- dealloc
1、alloc实现
经过一系列的调用,最终调用了C函数 calloc;
此时并没有设置引用计数位1;
2、retain实现

- 通过两次哈希查找;
- 第一次找到 SideTables 中对应的 SideTable 的位置,然后获取对应的引用计数表,
- 第二次查找是查找引用计数表中存储的引用计数值是 size_t 类型
- 然后对 size_t 进行加一操作。(其中,SIDE_RC_ONE 并非是1,而是4,因为前两位存储的并不是引用计数相关内容,需要右移2位)
3、release实现

- 与 retain 操作基本相同,最后进行减一操作
4、retainCount实现

- 首先声明一个局部变量为 1 ;
- 然后查找引用计数表中的引用计数
- 最后进行位与操作,获取引用计数
问题:新alloc的对象为什么引用计数为1?
解答:因为,新alloc出来的对象,在引用计数表中是没有数值的,所以上面 it->second 读出的值为0,又声明的局部变量为1,所以相加之后 retainCount 为1.
5、dealloc实现

object_dispse()的实现:

objc_destructInstance() (销毁实例对象)的实现:

clearDeallocating() 的实现;

六、弱引用管理

1、添加 weak 变量:

一个 __weak 修饰的对象,系统会调用objc_initWeak()方法对其处理,最终会调用 weak_register_no_lock()方法对其进行具体操作,通过弱引用对象进行一个哈希运算查找到在对应弱引用表的位置,若此位置有弱引用数组则添加新的对象到数组中,若没有则重新创建弱引用地址,在第0个位置添加新的weak指针,后面的初始化为0或者nil
源码解析:






2、清除weak变量,同时设置指向为nil的过程:

源码解析



当一个对象进行dealloc操作,系统会调用 weak_clear_no_lock() 方法对其操作,具体是系统会通过哈希算法查找对应的弱引用表中的位置,若找到则返回一个数组,数组中存储所有的弱引用关系表,然后系统对数组进行遍历,把所有的弱引用指针分别指向nil
七、自动释放出
问题:
- 请问array的内存是在什么时候释放的?

- AutoreleasePool的实现原理?
- AutoreleasePool为何可以嵌套使用?
1、AutoreleasePool
编译器会将@autoreleasepool{} 改写为

objc_autoreleasePoolPush:

objc_autoreleasePoolPop:

一次pop操作相当于一次批量的pop操作
2、AutoreleasePool的数据结构
- 是以栈为结点的双向链表的形式组合而成;
- 是和线程一一对应的
双向链表

栈
后入先出

AutoreleasePoolPage

原本的内存图:

发生push操作后:

[obj autorelease]的系统实现过程:

AutoreleasePoolPage::pop
- 根据传入的哨兵对象找到对应的文职
- 给上次 push 操作之后添加的对象依次发送 release 消息
- 回退到 next 指针的正确位置
自动释放池总结
- 在当次的 RunLoop 将要结束的时候调用 AutoreleasePoolPage::pop();
- 多层嵌套就是多次插入哨兵对象;
- 在 for 循环中 alloc 图片数据等内存消耗较大的场景手动插入 AutoreleasePool,降低内存的峰值(使用场景)
八、循环引用
三种循环引用:
- 自循环引用
- 相互循环引用
- 多循环引用
1、自循环引用

2、相互循环引用

3、多循环引用

注意点:
- 代理
- block
- NSTimer
- 大循环引用
如何破除循环引用?
- 避免产生循环引用(如使用弱引用修饰)
- 在合适的时机断掉循环引用
具体解决方案
- __weak
- __block
- __unsafe_unretained
1、__weak 破解

2、__block 破解
注意:
- MRC 下,__block 修饰的对象不会增加其引用计数,避免了循环引用
- ARC 下,__block 修饰的对象会被强引用,无法避免循环引用,需手动解决
3、__unsafe_unretained 破解
- 修饰对象不会增加其引用计数,避免循环引用
- 如果被修饰对象在某一时机被释放,此时再次访问时,会产生悬垂指针,导致内存泄漏
NSTimer 的循环引用问题
1、若非循环 timer
可将 timer 设置无效,然后置空
2、循环 timer
设置中间对象,中间对象持有对 timer 和 VC 的弱引用变量。NSTimer 分派的回调是在中间变量中实现。在中间变量回调的方法中对其所持有的 target 进行判断,若当前值存在则将NSTimer的值回调给原对象;若不存在(已经被释放),则设置timer为无效状态,具体代码实现如下:

(修改系统方法实现)

(完成改写)

总结
- 什么是 ARC ?
- 为什么 weak 指针指向的对象在被废弃之后会被自动置为 nil ?
- 苹果是如何实现 AutoreleasePool 的?
- 什么是循环引用?有哪些循环引用?怎样解决?
网友评论