来,来,来看看这个平时不用,其实它一直那里的Autoreleasepool
长什么样~
其实在main.m
文件入口就已经给我们加好了@autoreleasepool
的内容是这样的:
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
所以整个 iOS 的应用都是包含在一个自动释放池 block 中的。
实际工作时其实是这样的:
int main(int argc, const char * argv[]) {
{
void * atautoreleasepoolobj = objc_autoreleasePoolPush();
// do whatever you want
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
return 0;
}
那objc_autoreleasePoolPush
和objc_autoreleasePoolPop
又是啥呢?
void *objc_autoreleasePoolPush(void) {
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt) {
AutoreleasePoolPage::pop(ctxt);
}
就是AutoreleasePoolPage
的两个push
和pop
方法,接下来我们这三个一个个看到底都是些啥玩意儿。
AutoreleasePoolPage
先看看 AutoreleasePoolPage
是个啥玩意儿呢?
其实,autoreleasepool
是没有单独的内存结构的,每一个自动释放池都是由一系列的 AutoreleasePoolPage
组成的,并且每一个AutoreleasePoolPage
的大小都是 4096 字节(16 进制 0x1000)
AutoreleasePoolPage {
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
}
-
magic
用来校验AutoreleasePoolPage
结构是否完整; -
next
指向第一个可用的地址; -
thread
指向当前的线程; -
parent
指向父类 -
child
指向子类
Push
static inline void *push()
{
//autoreleaseFast 关键
id *dest = autoreleaseFast(POOL_SENTINEL);
assert(*dest == POOL_SENTINEL);
return dest;
}
再看autoreleaseFast
执行具体的插入操作
static inline id *autoreleaseFast(id obj)
{
//hotPage 可以理解为当前正在使用的 AutoreleasePoolPage
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
//当前 page 存在且没有满时,直接将对象添加到当前 page 中
return page->add(obj);
} else if (page) {
//当前 page 存在且已满时,创建一个新的 page ,并将对象添加到新创建的 page 中
return autoreleaseFullPage(obj, page);
} else {
//当前 page 不存在时,即还没有 page 时,创建第一个 page ,并将对象添加到新创建的 page 中
return autoreleaseNoPage(obj);
}
}
page->add
添加对象
id *add(id obj) {
id *ret = next;
*next = obj;
next++;
return ret;
}
这个方法其实就是一个压栈的操作,将对象加入 AutoreleasePoolPage
然后移动栈顶的指针
autoreleaseFullPage
(当前 hotPage
已满)
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);
}
它会从传入的 page
开始先遍历整个双向链表
- 一直遍历,直到找到一个未满的
AutoreleasePoolPage
, - 如果找到最后还没找到,就新建一个
AutoreleasePoolPage
- 将该页面标记成
hotPage
- 调动
page->add
方法添加对象。
autoreleaseNoPage
(没有 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);
}
POOL_SENTINEL
是一个边界对象 nil
,用来区别每个page
即每个 AutoreleasePoolPage
边界
如果当前内存中不存在 AutoreleasePoolPage
,就要构建一个新的自动释放池的双向链表,将当前页标记为 hotPage
。但是第一个 AutoreleasePoolPage
是没有parent
指针的,所以会先向这个page
中添加一个POOL_SENTINEL
对象,来确保在pop
调用的时候,不会出现异常。
push
小结
一个 push
操作其实就是创建一个新的 autoreleasepool
,对应 AutoreleasePoolPage
的具体实现就是往 AutoreleasePoolPage
中的 next
位置插入一个 POOL_SENTINEL
,并且返回插入的POOL_SENTINEL
的内存地址。
Autorelease
- [NSObject autorelease]
└── id objc_object::rootAutorelease()
└── id objc_object::rootAutorelease2()
└── static id AutoreleasePoolPage::autorelease(id obj)
└── static id AutoreleasePoolPage::autoreleaseFast(id obj) //重点
├── id *add(id obj)
├── static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
│ ├── AutoreleasePoolPage(AutoreleasePoolPage *newParent)
│ └── id *add(id obj)
└── static id *autoreleaseNoPage(id obj)
├── AutoreleasePoolPage(AutoreleasePoolPage *newParent)
└── id *add(id obj)
autorelease
跟 push
操作的实现非常相似。只不过 push
操作插入的是一个POOL_SENTINEL
,而 autorelease
操作插入的是一个具体的autoreleased
对象。
Pop
先放一张图:
pop
在内存中的变化就是长这个样子。将边界对象指向的这一页 AutoreleasePoolPage
内的对象释放
回头看一看 objc_autoreleasePoolPop
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
pop
函数的入参就是 push
函数的返回值,也就是 POOL_SENTINEL
的内存地址,即pool token
。当执行pop
操作时,内存地址在 pool token
之后的所有autoreleased
对象都会被 release
。直到pool token
所在 page 的 next 指向 pool token
为止。
static inline void pop(void *token) {
//获取当前 token 所在的 AutoreleasePoolPage
AutoreleasePoolPage *page = pageForPointer(token);
id *stop = (id *)token;
//释放栈中的对象,直到 stop
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
-- 用循环持续释放AutoreleasePoolPage
中的内容(objc_release
),直到next
指向了stop
-
child
的kill
方法
--它会将当前页面以及子页面全部删除
总结
AutoreleasePool
= AutoreleasePoolPage (4096字节) * n
;
AutoreleasePoolPage
= push
+ autorelease
+pop
;
push
和autorelease
最终都是调用 autoreleaseFast
方法,变了花的往next
位置插POOL_SENTINEL
或对象
pop
传入边界对象,然后对page
中的对象发送release
的消息
其实
通常情况下,我们是不需要手动添加 autoreleasepool
的,使用线程自动维护的 autoreleasepool
就好了。根据苹果官方文档中对 Using Autorelease Pool Blocks
的描述,我们知道在下面三种情况下是需要我们手动添加 autoreleasepool
的:
如果你编写的程序不是基于 UI 框架的,比如说命令行工具;
如果你编写的循环中创建了大量的临时对象;
如果你创建了一个辅助线程。
参考资料
What is autoreleasepool? [duplicate]
NSAutoreleasePool
iOS之autoreleasepool详解
自动释放池的前世今生 ---- 深入解析 Autoreleasepool
各个线程 Autorelease 对象的内存管理