封面
来,来,来看看这个平时不用,其实它一直那里的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指向子类
autorelease双向链表
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.png
其实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 对象的内存管理












网友评论