美文网首页
iOS 自动释放池(AutoreleasePool)

iOS 自动释放池(AutoreleasePool)

作者: yx_yang | 来源:发表于2023-08-21 16:34 被阅读0次

NSAutoreleasePool是OC中提供的一个类。

新建一个项目后,系统会默认在主线程创建一个Runloop并开启,runloop在监听到交互事件后,会自动创建一个NSAutoreleasePool,在准备休眠时,会释放旧的并创建新的,在即将退出runloop时,会释放自动释放池。

在NSAutoreleasePool中包含一个可变数组,用来存储被声明为autorelease的对象。当NSAutoreleasePool自身被销毁时,它会遍历这个数组,release数组中的所有对象,如果对象的引用计数在release后等于0,则对象也会在之后被销毁。

被标记为autorelease的对象会跟最近的NSAutoreleasePool匹配,可以嵌套使用NSAutoreleasePool。

工作原理

NSAutoreleasePool可以将加入AutoreleasePool中的变量release的时机延迟,当创建一个对象,在正常情况下,变量会在超出其作用域后被release。如果将对象加入到了NSAutoreleasePool中,这个对象并不会立即释放,而是会等到runloop休眠或超出autoreleasepool作用域之后才会被释放。

1、从程序启动到加载完成,主线程对应的runloop会处于休眠状态,等待用户交互来唤醒runloop

2、用户的每一次交互都会启动一次runloop,用于处理用户的所有点击、触摸事件等

3、runloop在监听到交互事件后,就会创建自动释放池,并将所有延迟释放的对象添加到自动释放池中

4、在一次完整的runloop结束之前,会向自动释放池中所有对象发送release消息,然后销毁自动释放池

结构

NSAutoreleasePool其实就是一个以autoreleasePoolPage 为结点的双向链表,每一个线程的autoreleasePool就是一个指针的堆栈,每一个指针代表一个需要release的对象或者哨兵对象(只有一个),autoreleasePoolPage会随时需要存放的内容的多少而增加或删除。     

结构图

原理

自动释放池的压栈和出栈主要是通过结构体的构造函数和析构函数调用底层的objc_autoreleasePoolPudh和objc_autoreleasePoolPop,实际上是调用AutoreleasePoolPage的push和pop两个方法

每次调用push操作其实就是创建一个新的AutoreleasePoolPage,而AutoreleasePoolPage的具体操作就是插入一个哨兵对象,并返回插入哨兵对象的内存地址。而push内部调用autoreleaseFast方法处理,主要有以下三种情况:

1.当page存在,且不满时,调用add方法将对象添加至page的next指针处,并next递增

2.当page存在,且已满时,调用autoreleaseFullPage初始化一个新的page,然后调用add方法将对象添加至page栈中

3.当page不存在时,调用autoreleaseNoPage创建一个hotPage,然后调用add方法将对象添加至page栈中

一个page会有一定的空间(4096-56)来装入对象,当page的空间还没有 满,还可以被装入对象时,这个page就是hotPage;表明再有对象要被装入,就会装到这个Page中,

如果这个page被装满了,next会=end,同时创建一个新的poolPage2,page的child指向这个新的poolPage2. page会变成coldpage,新的poolPage2成为hotPage

当执行pop操作时,会传入一个值,这个值就是push操作的返回值,即哨兵对象的内存地址token。所以pop内部的实现就是根据token找到哨兵对象所处的page中,然后使用 objc_release释放token之前的对象,并把next指针到正确位置

使用场景

1.降低内存峰值

首先要区分,在以前MRC时,不是所有初始化方法都会做到降低内存峰值。

MRC场景1 MRC场景2

场景1所示,这种初始化方式通过添加自动释放池的方式,可以降低内存峰值。

场景2所示,这种初始化方式,即使添加了自动释放池,也不会降低内存峰值(可以通过runtime源码调试或instruments查看)。

根据MRC的原理我们可以知道,通过alloc、new等方式创建的对象,需要手动添加release,通过类方法构造器创建的对象一般可以不用,这是因为系统在这些创建方式内自动添加了autorelease。

也因此,场景1中自动添加了autorelease的对象,其实是已经加入了系统默认的自动释放池,而这个自动释放池,是需要等到当前runloop休眠或停止时才会释放,因此在这个for循环的整一个函数内,所有创建的对象都不会释放。在添加了自己的自动释放池后,其实是进行了自动释放池的嵌套调用,创建的对象会受自己最近的自动释放池管理,在自动释放池的作用域结束之后,就会释放该对象。

场景2中的对象只是单纯进行了初始化和引用计数+1操作,本身不受系统默认自动释放池管理,对象会在对象当前作用域结束之后马上dealloc。

在ARC下,可以直接使用来降低内存峰值


ARC下UIImage

2.对数组的遍历操作可以使用系统推荐的api

使用数组的enumerateObjectsUsingBlock等遍历方式,可以看到系统内部封装了autoreleasepool的操作,所以虽然forin的快速遍历方式是最快的,但是enumerateObjectsUsingBlock等方式在特定场合还是很合适的。

push、pop

3.在子线程中使用

如果我们创建了一个常驻线程,或者是非GCD方式创建子线程,那么需要在入口处添加自动释放池(GCD方式创建子线程默认添加自动释放池)。

因为子线程的runloop默认是不启动的,那么对象的释放就只能等到线程被回收,这样就会持续占用内存空间,所以需要加入自动释放池来及时释放资源。

在子线程中如果手动创建了autoreleasepool,那么创建的autorelease 对象就会交给 pool 去管理。如果没有手动创建pool,就会调用autoreleaseNoPage 方法,自动创建一个AutoreleasePoolPage,然后将对象添加到AutoreleasePoolPage的栈中。

相关文章

网友评论

      本文标题:iOS 自动释放池(AutoreleasePool)

      本文链接:https://www.haomeiwen.com/subject/ryzkmdtx.html