OC内存管理

内存管理

一、内存布局

内存布局
内存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、引用计数表

  • 是一个哈希表。
  • 通过哈希查找。
  • 通过指针查找引用计数。
引用计数查找1
引用计数查找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实现

retain

5.3、release实现

release

5.4、retainCount实现

retainCount

这里解答了alloc为什么引用计数没有加1,而retainCount获取的数值却为1。

5.5、dealloc实现(重点)

5.5.1、 dealloc实现流程图

dealloc

5.5.2、 object_dispose实现

object_dispose

5.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_initWeak

weak_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_autoreleasePoolPush

objc_autoreleasePoolPop代码

objc_autoreleasePoolPop

7.2、自动释放池数据结构

7.2.1、概念
数据结构

双向链表

双向链表


7.2.2、AutoreleasePoolPage结构
AutoreleasePoolPage

由上面可以看到:AutoreleasePool是和线程一一对应的

AutoreleasePoolPage内存地址:

内存地址

7.2.3、AutoreleasePoolPage::push实现
AutoreleasePoolPage::push

AutoreleasePoolPage::push相当于是在当中插入哨兵对象,做标记。

7.2.4、[obj autorelease]实现过程
autorelease
调用autorelease
7.2.5、AutoreleasePoolPage::pop
AutoreleasePoolPage::pop

相当于是将next指针哨兵对象之间的内容,全部release。

发送release消息

next指针回退

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例题就可以。

你可能感兴趣的:(OC内存管理)