自动释放池是 oc 的一种内存自动回收机制,在MRC 中可以使用AutoReleasePool进行延迟释放,在ARC中 可以用 AutoReleasePool 将对象添加到最近的自动释放池,不会立即释放,需要等到 runloop 休眠或者 超出 AutoReleasePool{} 作用域之后才会被释放
@autoreleasepool{} 编译之后发现是一个c++结构体 里面有构造函数 push 也有一个析构函数 pop
官方文档描述:
自动释放池是一个关于指针的栈结构 其中的指针是指向释放的对象或者pool_boundary哨兵(边界) 自动释放池是一个页的结构(似虚拟内存的结构)是一个双向链表,有父节点和字节点 自动释放池和线程有关系
AutoReleasePoolPage
objc_aotureleasePoolPush->autoreleasePage::push()
AutoReleasePool 是一个页 也是一个对象,这个页大小为4096 autoreleasePage继承autoreleasePageData,且该类的属性也是来自父类,有当前线程、父节点 字节点,next对象 深度depth hiwat:最大入栈数量标记
AutoReleasePoolPush
AutoReleasePoolPush:首先进行判断是否存在pool,如果没有则通过 autoreleasenewpool:方法创建pool,如果有 则通过 autoreleasefast 压栈 哨兵对象
autoreleasenewpool: 如果有当前线程操作页hotpage(),则进行autoreleaseFullpage(obj,page)压栈对象,如果没有则通过 autoreleasenopage 进行创建页
autoreleasenopage:当前页不存在->则通过autoreleasepoolpage创建当前线程的自动释放池(autoreleasepoolpage 的构造方法是通过autoreleasenopagedata初始化方法实现的)
autoreleasepoolpage 构造方法{ begin() 压栈位置(下一个要释放对象的压栈地址)页首地址 + 56 56就是结构体 autoreleasepoolpagedata 的大小 objc_thread_self() 当前线程 newparent 父节点 depth 父节点深度 hiwat 最大入栈数 }
查看自动释放池内存结构
一个页有 4096 个字节,而自动释放池本身结构体占用 56 个,所以一个页中实际可以存储 4040 字节,一个对象指针 8字节,所以一共可以存储 505 个对象,但是第一页又一个哨兵对象,所以 第一页只能存储 504个对象
autoreleasepool 是一个结构体对象,是多个页构成的双向链表的栈结构,先进后出的原则
压栈对象 autoreleaseFast
获取当前操作页hotpage(),如果未满则 paga->add(obj)压栈,如果满了则安排新的栈 autoreleaseFullPage(obj,page),如果没有查到页 则创建新的页autoreleaseNoPage(obj)
autoreleaseFullPage
本质是一个 do while 循环,如果当前已满,查询字节点,如果字节点不存在则新建页autoreleasepoolpage(page)并设为当前页 并压栈,如果存在且未满则终止循环作为当前页 并压栈
add(obj)
将对象压栈道next指针的位置,然后对next进行++,即下一个对象存储的位置,通过栈结构存储
AutoReleasePoolPop 分析
空页面返回,如果存在page 首先进行容错处理,然后调用poppage出栈页 在这个方法中通过 releaseuntil 出栈当前页 stop 位置之前的所有对象,向栈中发送 release 消息,直到遇到传入的哨兵对象
releaseuntil 出栈
主要是循环遍历,释放掉 stop 位置之前的所有对象,首先通过获取page的next释放对象(page的最后一个对象),并对 next进行递减 获取上一个对象,断是否为哨兵对象,如果不是哨兵对像 则自动调用 objc_release释放
kill
销毁当前页,将当前页赋值为父节点页,并将父节点页的child对象指针设置为nil
总结
在自动释放池的压栈中,当没有pool时即只有空占位符时,创建页,压栈哨兵对象,当页中压栈普通对象时,主要通过next指针递减进行的,当栈满了 需要设置页的child对象为新建页,流程如下:
在自动释放池的出栈中,在页的出栈普通对象主要通过next指针递减进行的,当空页时,需要赋值页的parent对象为当前页继续出栈,直到找到 stop 且不是哨兵对像,删除空的当前子页面
设置父页面为当前页