一、内存布局
内存布局 内存1 内存2二、内存管理方案
2.1、方案介绍
- TaggedPointer
比如NSNumber类型 - NONPOINTER_ISA
针对64位架构 - 散列表
包括引用计数表和弱引用计数表
2.2、散列表
散列表问题1:为什么不是一个SideTable?
存在效率问题。操作其中一个对象引用计数时,会被加锁,后面对象就会要一直等待。
为了解决上述问题:引入了分离锁的计数解决方案,提高访问效率。
问题2:怎样通过一个对象指针快速定位属于哪个Side Tables?
通过哈希查找、不涉及遍历、查找效率高。
Side Tables的本质是一张Hash表。
三、数据结构
3.1、Spinlock_t(自旋锁)
- Spinlock_t是“忙等”的锁。
- 适用于轻量访问。
3.2、引用计数表
- 是一个哈希表。
- 通过哈希查找。
- 通过指针查找引用计数。
3.3、弱引用计数表
弱引用计数表四、MRC和ARC相关
4.1、MRC(手动引用计数)
MRC其中红色部分不能在MRC下使用!
4.2、ARC(自动引用计数)
ARC五、引用计数管理
- alloc
- retain
- release
- retainCount
- dealloc
5.1、alloc实现
经过一系列调用,最终调用了C函数calloc。
此时并没有设置引用计数为1。
5.2、retain实现
retain5.3、release实现
release5.4、retainCount实现
retainCount这里解答了alloc为什么引用计数没有加1,而retainCount获取的数值却为1。
5.5、dealloc实现(重点)
5.5.1、 dealloc实现流程图
dealloc5.5.2、 object_dispose实现
object_dispose5.5.3、 objc_destructInstance实现
objc_destructInstance问题3:通过关联对象为一个类添加实例变量,在对象的dealloc方法中是否有必要对关联对象进行移除?
没必要,因为系统已经进行移除了(如上图)。
5.5.4、 clearDeallocating实现
clearDeallocating问题4:为什么一个弱引用对象指针,在对象销毁后,会自动置为nil?
1、因为在对象销毁后,会调用dealloc方法。
2、在dealloc内部方法实现中,会调用weak_clear_no_lock()函数。
3、在该函数内部,会根据当前对象指针,利用哈希算法,查找弱引用表,然后取出弱引用数组,遍历这个数组,将弱引用指针全部置为nil。
六、弱引用管理
问题5:一个weak变量是怎样被添加到弱引用计数表中的?
解释:
这里讲解一下weak修饰的变量会经历哪些步骤就可以了。
1、一个被声明为weak的变量,经过编译器编译后,会调用objc_initWeak()、storeWeak()、weak_register_no_lock()函数。
2、在weak_register_no_lock()函数中,进行一个弱引用添加。具体添加的位置,是通过哈希算法查找的。
6.1、系统内部函数调用
weak编译 objc_initWeakweak_register_no_lock()
通过哈希算法,查找对应位置;将弱引用weak变量添加到弱引用计数表中。
6.2、清除变量,弱引用指针指向nil
dealloc内部调用这里主要是weak_clear_no_lock起到主要作用。
七、自动释放池
问题6:下面的array什么时候释放
自动释放池在当次runloop将要结束的时候,对前一次的AutoreleasePool进行pop操作,同时会push一个新的AutoreleasePool。
所以,当前的array会在当前runloop将要结束的时候,调用AutoreleasePoolPage::pop,对对象进行释放。
问题7:AutoreleasePool的实现原理是怎样的?
是以栈为节点通过双向链表 的形式组合而成。
问题8:AutoreleasePool为何可以嵌套使用?
因为多次嵌套在底层的反应是:多次插入哨兵对象。如果当前AutoreleasePoolPage没有满,当然可以插入对象。
问题9:什么是自动释放池?
是以栈为节点通过双向链表 的形式组合而成。
问题10:AutoreleasePool使用场景
在for循环中alloc图片数据等内存消耗大的场景(需要创建很多临时对象),可以手动插入AutoreleasePool。
7.1、自动释放池代码转换
代码转换objc_autoreleasePoolPush代码
objc_autoreleasePoolPop代码
7.2、自动释放池数据结构
7.2.1、概念
数据结构双向链表
栈
7.2.2、AutoreleasePoolPage结构
AutoreleasePoolPage由上面可以看到:AutoreleasePool是和线程一一对应的。
AutoreleasePoolPage内存地址:
7.2.3、AutoreleasePoolPage::push实现
AutoreleasePoolPage::pushAutoreleasePoolPage::push相当于是在栈当中插入哨兵对象,做标记。
7.2.4、[obj autorelease]实现过程
autorelease 调用autorelease7.2.5、AutoreleasePoolPage::pop
AutoreleasePoolPage::pop相当于是将next指针到哨兵对象之间的内容,全部release。
7.3、自动释放池总结
总结八、循环引用
8.1、循环引用类型
循环引用类型8.2、场景
场景8.3、破除循环引用思路
思路8.4、循环引用方案
方案-
__weak破解
__weak -
__block破解
__block -
__unsafe_unretained破解
__unsafe_unretained
8.5、循环引用示例
8.5.1、 Block示例
参考Block示例
8.5.2、 NSTimer示例
先来解释为什么NSTimer能够产生循环引用?
NSTimer
1、因为对象拥有NSTimer,所以要对其强引用。
2、因为NSTimer持有它的target,所以NSTimer强引用对象。
3、通过对象弱引用NSTimer,达不到目的。
因为主线程的Runloop,长驻内存➡️Runloop强引用NSTimer➡️NSTimer强引用对象➡️所以当前对象很难释放。
解决方案:
- 非重复性定时器
//取消定时器
[timer invalidate];
timer = nil;
-
重复性定时器
重复性定时器
设置中间对象,判断中间对象所持有的目标对象是否为nil(这里利用到了weak指针会自动为nil)。如果为nil,则执行以下代码:
//取消定时器
[timer invalidate];
timer = nil;
九、内存管理总结
问题11:什么是ARC?
ARC是由LLVM编译器和Runtime共同协作,为我们实现自动引用计数管理。
问题12:苹果是如何实现AutoreleasePool的?
是以栈为节点通过双向链表的形式组合而成。
问题13:什么是循环引用?你遇到过哪些循环引用,是怎样解决的?
上面讲的NSTimer例题就可以。
网友评论