1. 内存布局
stack
: 栈区, 方法调用. (高地址)heap
: 堆区, 通过alloc等分配的对象bss
: 未初始化的全局变量等data
: 已初始化的全局变量等text
: 程序代码
2. 内存管理方案
系统方案
TaggedPointer
(小对象: 如NSNumber
)NONPOINTER_ISA
(非指针型isa, arm64架构程序)- 散列表(
side tables
) (spinlock_t
(自旋锁),RefcountMap
(引用计算表),weak_table_t
(弱引用表))
快速分流
Side Tables
的本质是一张Hash表
. [对象指针(Key) ==(hash函数)=>Side Table
(Value)]- Hash查找: 给定值是对象的内存地址, 目标值是数组下标索引.
3. 数据结构
Spinlock_t
- “忙等”的锁
- 适用于轻量访问
RefcountMap
- hash表实现, 提高查找效率.
ptr
==(DisguisedPtr(objc_object)
) ==>size_t
weak_table_t
- 对象指针(key) ==(hash函数)==>
weak_entry_t
(value)
4. MRC
- 手动引用计数
- (MRC特有)
retain
,release
,retainCount
,autorelease
, alloc
,dealloc
5. ARC
- 自动引用计数
- ARC是
LLVM
(编译器)和Runtime
协作的结果. - ARC中禁用手动调用
retain
,release
,retainCount
,dealloc
(不能显式调用[super dealloc]
) - ARC中新增weak, strong属性关键字
6. 引用计数管理
alloc实现
- 经过一系列的函数调用, 最终调用C函数calloc
- 此时并没有设置引用计数为1
retain实现
1 | // 两次hash查找 |
release实现
1 | SideTable & table = SideTables()[this]; |
retainCount实现
1 | SideTable & table = SideTables()[this]; |
dealloc实现 (重点)
是否可以释放的判断条件
- nonpointer_isa
- weakly_referenced
- has_assoc
- has_cxx_dtor
- has_sidetablt_rc
条件都不满足直接调用C函数free()
否则调用objc_dispose()
objc_dispose
内部实现
objc_destructInstance()
- C函数
free()
objc_destructInstance()
内部实现
hasCxxDtor
->object_cxxDestruct
hasAssociatedObjects
->_object_remove_asscations()
clearDeallocating()
clearDeallocating()
内部实现
1 | ... |
7. 弱引用管理
添加weak变量
object_initWeak()
-> storeWeak()
-> weak_register_no_lock()
清除weak变量, 同时设置指向为nil
dealloc()
-> … -> weak_clear_no_lock()
8. 自动释放池
@autoreleasepool
1 | // @autoreleasepool{} 是下面两个方法的封装回调 |
数据结构
- 是以
栈
为节点通过双向链表的形式组合而成 - 是和线程一一对应的
疑点
1. viewdidload中创建的对象什么时候释放?
1 | 在当次runloop将要结束的时候, 调用AutoreleasePoolPage::pop()操作 |
2. @autoreleasepool 为什么可以嵌套调用?
1 | 多层嵌套就是多次插入哨兵对象 |
3. @autoreleasepool使用场景
1 | 在for循环中alloc图片数据等内存消耗较大的场景手动插入autoreleasepool |
9. 循环引用
分3中类型
- 自循环引用 (对象强持有(strong)成员变量, 成员变量再被赋值当前对象)
- 相互循环引用
- 多循环引用
常见场景
- 代理
Block
NSTimer
- 大环引用
如何解除
- 避免产生循环引用, (
__weak
,__block
,__unsafe_unretained
(因无引用计数, 效果同__weak
)) - 断环
关于__block
MRC
下,__block
修饰对象不会增加引用计数, 可以避免循环引用ARC
下,__block
修饰对象会被强引用, 无法避免循环引用, 需要手动解环
关于__unsafe_unretained
- 修饰对象不会增加引用计数, 可以避免循环引用
- 如果被修饰对象在某一时机被释放, 会产生
悬垂指针
NSTimer循环引用问题
总结
什么是ARC?
1 | ARC是由LLVM编译器和Runtime共同协作实现自动引用计数的结果 |
为什么weak指针指向的对象在废弃之后会被自动置为nil?
1 | 当一个对象被dealloc()之后, 在dealloc的内部实现中会调用弱引用清除函数 weak_clear_no_lock(), |