- 内存布局
- 内存管理方案
- 数据结构
- ARC & MRC
- 引用计数
- 弱引用
- 自动释放池
- 循环引用
内存布局
- stack: 方法调用
- heap: 通过alloc等分配的对象
- bss: 未初始化的全局变量等
- data: 已初始化的全局变量等
- text: 程序代码
内存管理方案
- TaggedPointer(小对象)
- NONPOINTER_ISA
- 散列表(弱引用表和引用计数表)
NONPOINTER_ISA
arm64架构
散列表 SideTables()结构
sideTable结构
为什么不是一个SideTable?
分离锁
怎样实现快速分流?
SideTables的本质是一张Hash表。
Hash查找 例:给定值是对象内存地址,目标值是数组下标索引。
数据结构
- Spinlock_t
- RefcountMap
- weak_table_t
Spinlock_t
- Spinlock_t是”忙等“的锁
- 适用于轻量访问
RefcountMap(哈希表提高查找效率)
size_t
weak_table_t(弱引用表)
ARC & MRC
MRC(手动引用计数)
ARC(自动引用计数)
- ARC是LLVM和Runtime协作的结果。
- ARC中禁止手动调用retain/release/retainCount/dealloc
- ARC中新增weak、strong属性关键字
引用计数管理
实现原理分析
- alloc
- retain
- release
- retainCount
- dealloc
alloc实现 经过一系列调用,最终调用了C函数calloc 此时并没有设置引用计数为1
retain实现 SideTable& table = SideTables()[this]; size_t& refcntStorage = table.refcnts[this]; refcntStorage += SIDE_TABLE_RC_ONE;(加上了对应的偏移量)
release实现 SideTable& table = SideTables()[this]; RefcontMap::iterator it = table.refcnts.find(this); it->second -= SIDE_TABLE_RC_ONE;
retainCount实现 SideTable& table = SideTables()[this]; size_t refcnt_result = 1; RefcontMap::iterator it = table.refcnts.find(this); refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
dealloc实现(重要)
object_dispose()实现
objc_destructInstance()实现
clearDeallocating()实现
弱引用管理
添加weak变量
清除weak变量,同时设置指向为nil
自动释放池
array何时释放?
- AutoreleasePool的实现原理是怎样的?
- AutoreleasePool为何可以嵌套使用?
编译器会将@autoreleasepool()改写为: void *ctx = objc_autoreleasePoolPush(); {}中的代码 objc_autoreleasePoolPop(ctx);
objc_autoreleasePoolPush的实现
objc_autoreleasePoolPop的实现
自动释放池的数据结构
- 是以栈为结点通过双向链表的形式组合而成。
- 是和线程一一对应的。
双向链表
栈
AutoreleasePoolPage
AutoreleasePoolPage::Push
[obj autorelease]
AutoreleasePoolPage::Pop
- 根据传入的哨兵对象找到对应的位置
- 给上次push操作之后添加的对象依次发送release消息
- 回退next指针到正确位置
清除之后指向正确的位置,上面两图。
自动释放池的总结
- 在当次runloop将要结束的时候调用AutoreleasePoolPage::Pop()。
- 多层嵌套就是多次插入哨兵对象。
- 在for循环中alloc图片数据等内存消耗比较大的场景手动插入autoreleasePool.
循环引用
- 自循环引用
- 相互循环引用
- 多循环引用
自循环引用
相互循环引用
多循环引用
考点
- 代理
- Block
- NSTimer
- 大环引用
如何破除循环引用?
- 避免产生循环引用
- 在合适的时机手动断环
破除循环引用具体的解决方案有哪些?
- __weak
- __block
- __unsafe_unretained
__weak破解
__block破解(ARC与MRC有区别)
- MRC下,__block修饰的对象不会增加其引用计数,避免了循环引用
- ARC下,__block修饰的对象会被强引用,无法避免循环引用,需手动解环
__unsafe_unretained(不建议用)
- 修饰对象不会增加引用计数,避免了循环引用
- 如果被修饰的对象在某一时机被释放,会产生悬垂指针。
循环引用示例
- Block的使用示例
- NSTimer使用示例
NSTimer的循环引用问题
解决方案
源码部分
内存管理总结
- 什么是ARC?
- 为什么weak指针指向的对象在废弃之后会被自动置为nil?
- 苹果是如何实现AutoreleasePool的?
- 什么是循环引用?你遇到过哪些循环引用,是怎样解决的?