AutoreleasePool 的总结
1.创建和释放时机问题
App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()
。
第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush()
创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop()
和 _objc_autoreleasePoolPush()
释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop()
来释放自动释放池。这个 Observer 的 order 是 2147483647(2^31-1
),优先级最低,保证其释放池子发生在其他所有回调之后。
在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。
2.AutorealeasePool内部结构
是一条双向链表,每个节点都是一个AutoreleasePoolPage
3.AutoreleasePoolPage的结构
AutoreleasePoolPage 是一个 C++ 中的类:
class AutoreleasePoolPage {
magic_t const magic; //用于对当前AutoreleasePoolPage
完整性的校验
id *next; // 下一个可插入对象的可用地址,如果next
指向的地址加入一个object
,它就会如下图所示移动到下一个为空的内存地址中:
pthread_t const thread; // 当前的线程
AutoreleasePoolPage * const parent; // 父指针
AutoreleasePoolPage *child; // 子指针
uint32_t const depth;
uint32_t hiwat;
};
每一个自动释放池都是由一系列的 AutoreleasePoolPage
组成的,并且每一个 AutoreleasePoolPage
的大小都是 4096
字节(16 进制 0x1000)
4.POOL_BOUNDARY(哨兵对象)
也有人称作 POOL_SENTINEL
-
POOL_BOUNDARY 只是 nil
的别名,
- 在每个自动释放池初始化调用 objc_autoreleasePoolPush
的时候,都会把一个 POOL_BOUNDARY push 到自动释放池的栈顶,并且返回这个 POOL_BOUNDARY 哨兵对象。
5.objc_autoreleasePoolPush 方法
void *objc_autoreleasePoolPush(void) {
return AutoreleasePoolPage::push();
} static inline void *push() {
return autoreleaseFast(objc);
}
在这里会进入一个比较关键的方法 autoreleaseFast
,第一次传入哨兵对象 POOL_SENTINEL
:
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {//page没有满,就把obj对象加到page
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
//有hotPage
并且当前page
已满
static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {
do {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full()); setHotPage(page);
return page->add(obj);
}
//无hotPage
static id *autoreleaseNoPage(id obj) {
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page); if (obj != POOL_SENTINEL) {
page->add(POOL_SENTINEL);
} return page->add(obj);
上述方法分三种情况选择不同的代码执行:
- 有
hotPage
并且当前page
不满- 调用
page->add(obj)
方法将对象添加至AutoreleasePoolPage
的栈中
- 调用
- 有
hotPage
并且当前page
已满- 1.它会从传入的
page
开始遍历整个双向链表,查找到一个未满的AutoreleasePoolPage,
- 2.调用
autoreleaseFullPage
初始化一个新的页 - 调用
page->add(obj)
方法将对象添加至AutoreleasePoolPage
的栈中
- 1.它会从传入的
- 无
hotPage
调用 autoreleaseNoPage 创建一个 hotPage
如果obj == POOL_SENTINEL,则直接page->add(obj) 将对象添加至
AutoreleasePoolPage
的栈中- 如果obj != POOL_SENTINEL,则先加入POOL_SENTINEL哨兵对象,再调用
page->add(obj)
方法将对象添加至AutoreleasePoolPage
的栈中
最后的都会调用 page->add(obj)
将对象添加到自动释放池中。
hotPage
可以理解为当前正在使用的 AutoreleasePoolPage
。
6.objc_autoreleasePoolPop 方法
static inline void pop(void *token) {
AutoreleasePoolPage *page = pageForPointer(token);
id *stop = (id *)token; page->releaseUntil(stop); if (page->child) {
if (page->lessThanHalfFull()) {
page->child->kill();
} else if (page->child->child) {
page->child->child->kill();
}
}
}
该静态方法总共做了三件事情:
- 使用
pageForPointer
获取当前token(哨兵对象),
所在的AutoreleasePoolPage
- 调用
releaseUntil
方法释放栈中的对象,直到stop,
- 调用
child
的kill
方法
- pageForPointer 获取 AutoreleasePoolPage
pageForPointer
方法主要是通过内存地址的操作,获取当前指针所在页的首地址:
将指针与页面的大小,也就是 4096 取模,得到当前指针的偏移量,因为所有的 AutoreleasePoolPage
在内存中都是对齐的:
p = 0x100816048
p % SIZE = 0x48
result = 0x100816000
而最后调用的方法 fastCheck()
用来检查当前的 result
是不是一个 AutoreleasePoolPage
。
- releaseUntil 释放对象
它的实现还是很容易的,用一个 while
循环持续释放 AutoreleasePoolPage
中的内容,直到 next
指向了 stop(哨兵对象)
。
使用 memset
将内存的内容设置成 SCRIBBLE
,然后使用 objc_release
释放对象
- kill() 方法
函数releaseUntil
,它在释放的时候其实会一直顺着parent
往前释放,只到找到参数stop
传入的地址,也就是说可能一次性释放好几个page
。
- 当前
page
为空,直接kill掉当前page
,然后把parent
设置为hotpage
; - 当前
page
为空,而且没有parent
,kill掉当前page
,hotpage
置为空; - 当前
page
不为空,但是有child
,如果当前page
的空间占用不到一半,释放child
,如果当前page
的空间占用超过一半,且child
还有child
,直接释放这个孙子辈的page
。(对于第三步注释中的解释是:keep one empty child if page is more than half fully)
注意:
releaseUntil 做的工作是擦除AutoreleasePoolPage所占用的内存区域的内容(通过memset),但是并没有置空child或者parent指针
kill做的工作是置空已经被releaseUntil是放过的page的child的,也就是释放child所指向的AutoreleasePoolPage占用空间
if (page->lessThanHalfFull()) { page->child->kill();
}
当前page的空间占用少于一半,释放掉child page占用的空间
else if (page->child->child) {
page->child->child->kill();
}
当前page的空间占用超过一半的,因为很可能会满了,就不释放child page的占用空间了,为了预留一个child page,满了的话我们就不用创建一个page再置为自己的child了,但是只要预留一页就够了,所以释放掉child的child,也就是只保留page和page+1页
系统 C函数: 使用extern 可以调用其他类的c函数 extern void _objc_autoreleasePoolPrint(void); 可以使用,用于打印autoreleasepool 存放的对象
参考博客:https://draveness.me/autoreleasepool/#AutoreleasePoolPage
AutoreleasePool 的总结的更多相关文章
- @autoreleasepool在MRC和ARC中的区别
对于@autoreleasepool {} (1)在ARC中会销毁所有在里面创建的对象,即使你用外面的Strong指针指向他 (2)在MRC中如果有外部的强指针指向,不会销毁对象,retainCoun ...
- autoreleasepool自动释放池
示例: @autoreleasepool { ; i[largeNumber; i++) { (因识别问题,该行代码中尖括号改为方括号代替) Person *per = [[Person alloc ...
- AutoReleasePool 和 ARC 以及Garbage Collection
AutoReleasePool autoreleasepool并不是总是被auto 创建,然后自动维护应用创建的对象. 自动创建的情况如下: 1. 使用NSThread的detachNewThread ...
- 深入剖析AutoreleasePool
[深入剖析AutoreleasePool] Objc的AutoreleasePool是一个首尾相连的内存链接,每块大小为1页(32位机上为4kb). 上面可以看到,parent指向父Pool,chil ...
- Objc中2维指针作为输出参数时由ARC及@autoreleasepool引发的血案
先看下面一个例子 #import <UIKit/UIKit.h> #import "AppDelegate.h" @interface Something : NSOb ...
- iOS 非ARC基本内存管理系列 4-autorelease方法和@autoreleasepool
1.autorelease 基本用法 对象执行autorelease方法时会将对象添加到自动释放池中 当自动释放池销毁时自动释放池中所有对象作release操作 对象执行autorelease方法后自 ...
- autoreleasepool的笔记
1.autoreleasepool总是会被问到,放在自动释放池中的对象合适被释放?理解不正确的答案:{}出了大括号.出了作用域等等.个人认为参考答案是,1.在不是手动添加的AutoreleasePoo ...
- iOS基本内存管理:autorelease和autoreleasepool
1.autorelease 基本用法 对象执行autorelease方法时会将对象添加到自动释放池中 当自动释放池销毁时自动释放池中所有对象作release操作 对象执行autorelease方法后自 ...
- cocos2D-x 3.5 引擎解析之--引用计数(Ref),自己主动释放池(PoolManager),自己主动释放池管理器( AutoreleasePool)
#include <CCRef.h> Ref is used for reference count manangement. If a classinherits from Ref. C ...
- Runloop与autoreleasePool联系
autoreleasePool自动释放池,ARC模式下,苹果会自动进行内存管理,不需要我们手动去管理内存.这对于苹果开发者来说,省去了很多事情,不用再每天为了内存管理浪费掉宝贵的开发时间.大家都知道, ...
随机推荐
- 机器学习(六):回归分析——鸢尾花多变量回归、逻辑回归三分类只用numpy,sigmoid、实现RANSAC 线性拟合
[实验1 回归分析] 一. 预备知识 使用梯度下降法求解多变量回归问题 数据集 Iris 鸢尾花数据集是一个经典数据集,在统计学习和机器学习领域都经常被用作示例.数据集内包含 3 类共 150 条记录 ...
- 【LeetCode】3.19 对称二叉树
101. 对称二叉树 给你一个二叉树的根节点 root , 检查它是否轴对称. 示例 1: 输入:root = [1,2,2,3,4,4,3] 输出:true 示例 2: 输入:root = [1 ...
- C# 从0到实战 基本类型
C#语言的基本类型 与大多数编程语言一样,C#也有自己的基本类型,也称为内置类型.下面的表格就简单阐述了这些类型. C# 类型关键字 .NET 类型 bool System.Boolean byte ...
- 深入理解 python 虚拟机:描述器的王炸应用-property、staticmethod 和 classmehtod
深入理解 python 虚拟机:描述器的王炸应用-property.staticmethod 和 classmehtod 在本篇文章当中主要给大家介绍描述器在 python 语言当中有哪些应用,主要介 ...
- 2023-04-10:给定两个正整数x、y,都是int整型(java里) 返回0 ~ x以内,每位数字加起来是y的数字个数。 比如,x = 20、y = 5,返回2, 因为0 ~ x以内,每位数字加起
2023-04-10:给定两个正整数x.y,都是int整型(java里) 返回0 ~ x以内,每位数字加起来是y的数字个数. 比如,x = 20.y = 5,返回2, 因为0 ~ x以内,每位数字加起 ...
- 2022-09-26:以下go语言代码输出什么?A:{“Time“: “2020-12-20T00:00:00Z“, “N“: 5 };B:“2020-12-20T00:00:00Z“;C:{“N“:
2022-09-26:以下go语言代码输出什么?A:{"Time": "2020-12-20T00:00:00Z", "N": 5 }:B: ...
- 2021-07-27:给定一个数组arr,长度为N,arr中的值只有1,2,3三种。arr[i] == 1,代表汉诺塔问题中,从上往下第i个圆盘目前在左;arr[i] == 2,代表汉诺塔问题中,从上
2021-07-27:给定一个数组arr,长度为N,arr中的值只有1,2,3三种.arr[i] == 1,代表汉诺塔问题中,从上往下第i个圆盘目前在左:arr[i] == 2,代表汉诺塔问题中,从上 ...
- phpstudy-sqlilabs-less-4
题目:GET - Error based - Double Quotes - String 基于错误的GET双引号字符型注入 可能的注入点(不全) ' " ) ') ...
- Abaqus结构仿真软件的非线性问题与解决方案
无论是什么FEA 软件,想要获得非线性问题的一些解决方法始终没有那么简单.遇到问题是很常见的,那么下面就来看看Abaqus用户克服这一类问题的解决方法吧. 1. 简化模型 从简化模型开始,通过逐渐添 ...
- odoo部署安全性问题
本文档描述在生产中或在面向Internet的服务器上设置Odoo的基本步骤.它是在安装之后进行的,对于没有在internet上公开的开发系统来说,它通常不是必需的.警告如果您正在设置公共服务器,请务必 ...