内存布局
- stack:方法调用。
- heap:通过alloc等分配的对象。
- text:程序代码。
- bss:未初始化的全局变量等。
- data:已初始化的全局变量等。
内存管理方案
TaggedPointer
Tagged Pointer通过在其最后一个bit位设置一个特殊标记,用于将数据直接保存在指针本身中。因为Tagged Pointer并不是真正的对象,我们在使用时需要注意不要直接访问其isa变量。
NONPOINTER_ISA
在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址 从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息
散列表
散射表里面包含了弱引用表和引用计数表。
散列表原理
sidetable是个链表结构,sidetables是个hash表,hash表都是利用数组方便查找和链表的方便插入删除特性构成,所以sidetables相当于一个数组,里面存储遮sidetable,sidetable中的存储结构就是weak_table_t,weak_table_t中存储着不同的对象weak_entry_t,weak_entry_t中存储这个对象的所有的弱引用指针。
SideTables结构
SideTable结构
为什么不是一个SideTable,而是SideTables?
- 因为修改数据时会加锁,如果所有对象的表放在同一个sideTable,那么加锁会太频繁,效率会受影响。
- 因为有多个SideTable,所以可以异步操作多张表,彼此之间影响会降低。
怎样实现快速分流(怎样通过一个对象快速定位它所在的表)?
- SideTables的本质是一张
Hash表。 Hash表查找的时间复杂度为O(1)。- 通过
对象地址与Hash表的count取模,获取目标值下标索引。
引用计数表
- 引用计数表是通过
Hash表来实现的。 Hash表查找的时间复杂度为O(1)。
引用计数的存储
- 如果指针是Tagged Pointer, 那么直接返回, 否则进入下一步
- 判断isa是否优化过
- 如果优化过, 那么最后isa的最后19位存储的是引用计数
- 如果isa没有优化过, 那么就会进入sidetable_retainCount函数, 获取sidetable中的引用计数
- 如果最后19位不足以存储, 那么多余的引用计数会存储到sidetable中, 同时将倒数第20位的值置为1, 就是has_sidetable_rc的值为1
- 如果has_sidetable_rc的值为1, 就会从sidetable_getExtraRC_nolock函数中取出sidetable中存储的引用计数
弱引用表
表的key为引用对象,value为一个数组,代表了所有weak指针。对用weak指针引用对象时,如果不存在那就往weak指针数组里面插入该weak指针。 当weak指针销毁时候,会从weak指针数组里面移除该weak指针。 当引用对象销毁的时候,会自动地把weak指针数组置为nil。
弱引用变量如何被添加到弱引用表中?
当一个对象被释放,weak变量是如何处理的?
- 根据
对象,查找到弱引用表,提取弱引用数组。 - 遍历其中所有
弱引用指针,并置为nil。
内存管理
MRC
| 对象操作 | OC中对应的方法 | 对应的 retainCount 变化| |
|---|---|---|
| 生成并持有对象 | alloc/new/copy/mutableCopy等 | +1 |
| 持有对象 | retain | +1 |
| 释放对象 | release | -1 |
| 废弃对象 | dealloc | 0 |
ARC
ARC 是苹果引入的一种自动内存管理机制,会根据引用计数自动监视对象的生存周期,实现方式是在编译时期自动在已有代码中插入合适的内存管理代码以及在 Runtime 做一些优化。
原理
retain实现
release实现
retainCount实现
dealloc实现
object_disponse()实现
cleanDeallocating()实现
dealloc函数原理
- isa是优化过的指针, 对象没有被弱引用, 没有关联对象, 没有c++析构函数, 没有将引用计数存到Sidetable中, 就会立即释放
- 否则调用object_dispose函数
- 进入object_dispose函数, 可以看到调用了objc_destructInstance函数
- 进入objc_destructInstance函数, 可以看到对objc的处理, 是在clearDeallocating函数中将弱指针置为nil的
- 进入clearDeallocating函数, 又可以看到两种情况
- 对象的isa没有优化过
- 当isa没有被优化过, 进入sidetable_clearDeallocating函数, 可以看到weak引用是存放到SideTable中的
- 存放在了SideTable的weak_table_t中
- 查看weak_table_t, 即weak会被存放到一个全局的散列表中
- 会通过weak_clear_no_lock函数, 对弱指针置为nil, 同时移除删列表中的weak记录
- 和优化过, 并且被弱指针引用 或者 将引用计数存放到了Sidetable中
- 如果isa被优化过, 并且对象被弱引用或者将引用计数存到Sidetable中, 就会调用clearDeallocating_slow函数
- 进入clearDeallocating_slow函数, 可以看到在函数中, 调用了weak_clear_no_lock函数, 并清空了引用计数
- 对象的isa没有优化过
自动释放池
autoreleasepool的实现原理
- 每个AutoreleasePoolPage对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址
- AutoreleasePoolPage中除了一开始的56个字节用来存储成员变量, 其他的所有内存空间都是用来存储被autorelease对象的地址
- 当一个AutoreleasePoolPage不够存储autorelease对象地址时, 就会在创建一个AutoreleasePoolPage
- 所有的AutoreleasePoolPage对象通过双向链表的形式连接在一起
autoreleasepool的运行机制
- 调用push方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址
- 调用pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY
- id *next指向了下一个能存放autorelease对象地址的区域
autoreleasepool的嵌套
Runloop和Autorelease
- iOS在主线程的Runloop中注册了2个Observer
- 第1个Observer
- 监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()
- 第2个Observer
- 监听了kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
- 监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()
循环引用
循环引用类型
- 自循环引用
- 相互循环引用
- 多循环引用
常见的循环引用
-
代理
- 使用weak类型
-
Block
- 使用__weak
-
NSTimer
-
可以使用一个中间对象来解决循环引用问题
-
Proxy不实现任何target调用的方法, 而是使用消息转发的方式, 将消息转发给target, 这样不论定时器调用任何方法, 都能交给target去执行 -
-
-
大环引用
- 使用weak类型