Autorelease|Autorelease Pool 知识汇总

一、Autorelease Pool是什么
AutoreleasePool(自动释放池)是OC中的一种内存自动回收机制,它可以延迟加入AutoreleasePool中的变量release的时机。在正常情况下,创建的变量会在超出其作用域的时候release,但是如果将变量加入AutoreleasePool,那么release延迟执行。
二、Autorelease对象到底什么时候被释放
很多人说在“当前作用域大括号结束时释放”,显然没有正确理解Autorelease机制
在没有手加 Autorelease Pool的情况下,Autorelease 对象是在当前的 runloop迭代结束 时释放的,而它能够释放的原因是系统在每个 runloop 迭代中都加入了 自动释放池Push和Pop
三、在MRC中,调用[obj autorelease]来延迟内存的释放是一件简单自然的事,ARC下,我们甚至可以完全不知道Autorelease就能管理好内存。而在这背后,objc和编译器都帮我们做了哪些事呢?
如下Demo:
#import // 生成两个全局weak变量用来观察实验对象 __weak NSString *weak_String; __weak NSString *weak_StringAutorelease; - (void)viewDidLoad { [super viewDidLoad]; @autoreleasepool { [self createStr]; NSLog(@"------in the autoreleasepool------"); NSLog(@"%@", weak_String); NSLog(@"%@\n\n", weak_StringAutorelease); } NSLog(@"------in the main()------"); NSLog(@"%@", weak_String); NSLog(@"%@\n\n", weak_StringAutorelease); }-(void)createStr { // 创建常规对象 NSString *string = [[NSString alloc] initWithFormat:@"Hello, World!"]; // 创建autorelease对象 NSString *stringAutorelease = [NSString stringWithFormat:@"Hello, World! Autorelease"]; weak_String = string; weak_StringAutorelease = stringAutorelease; NSLog(@"------in the createString()------"); NSLog(@"%@", weak_String); NSLog(@"%@\n\n", weak_StringAutorelease); }

运行结果如下:
Autorelease|Autorelease Pool 知识汇总
文章图片
屏幕快照 2019-02-21 下午2.50.03.png
  • 首先在createString函数中创建了一个常规NSString对象和一个autorelease对象,然后分别赋值给两个weak全局变量用于观察目标对象。通过打印结果可以看到,在createString函数中两个对象都是正常存在的,出了createString函数在autoreleasepool中,常规对象已经被释放,而autorelease对象依然存在。在autoreleasepool外,autorelease对象也被释放了。
四、AutoreleasePool 的底层实现
  • 苹果通过声明一个__AtAutoreleasePool类型的局部变量__autoreleasepool实现了@autoreleasepool{}
  • 看看__AtAutoreleasePool的定义:
extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void); extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *); struct __AtAutoreleasePool { __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush(); } ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj); } void * atautoreleasepoolobj; };

我们可以分析出,单个自动释放池的执行过程就是:
objc_autoreleasePoolPush() —> [object autorelease] —> objc_autoreleasePoolPop(void *)
五、AutoreleasePoolPage
objc_autoreleasePoolPush()函数的实现:
void * objc_autoreleasePoolPush(void) { if (UseGC) return nil; return AutoreleasePoolPage::push(); }

同样objc_autoreleasePoolPop()函数的实现:
void objc_autoreleasePoolPop(void *ctxt) { if (UseGC) return; AutoreleasePoolPage::pop(ctxt); }

我们发现这两个函数的实现都是调用了AutoreleasePoolPage类中的方法。于是我们可以断定,AutoreleasePool是通过AutoreleasePoolPage类来实现的。
看看AutoreleasePoolPage定义的属性:
magic_t const magic; id *next; pthread_t const thread; AutoreleasePoolPage * const parent; AutoreleasePoolPage *child; uint32_t const depth; uint32_t hiwat;

1、AutoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage以双向链表的形式组合而成(分别对应结构中的parent指针child指针
2、AutoreleasePool是按线程一一对应的(结构中的thread指针指向当前线程)
3、AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址
4、上面的id *next指针作为游标指向栈顶最新add进来的autorelease对象的下一个位置,一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的page加入
5、栈中存放的指针指向加入需要release的对象或者POOL_SENTINEL(哨兵对象,用于分隔autoreleasepool)。
6、栈中指向POOL_SENTINEL的指针就是autoreleasepool的一个标记。当autoreleasepool进行出栈操作,每一个比这个哨兵对象后进栈的对象都会被release

Autorelease|Autorelease Pool 知识汇总
文章图片
image
  • 当 next == begin() 时,表示 AutoreleasePoolPage 为空;
    当 next == end() 时,表示 AutoreleasePoolPage 已满。
下面来看看实现AutoreleasePool的几个关键函数是如何实现的
AutoreleasePoolPage::push()
static inline void *push() { id *dest; if (DebugPoolAllocation) {// 区别调试模式 // Each autorelease pool starts on a new pool page. // 调试模式下将新建一个链表节点,并将一个哨兵对象添加到链表栈中 dest = autoreleaseNewPage(POOL_SENTINEL); } else { dest = autoreleaseFast(POOL_SENTINEL); // 添加一个哨兵对象到自动释放池的链表栈中 } assert(*dest == POOL_SENTINEL); return dest; }

static inline id *autoreleaseFast(id obj) { AutoreleasePoolPage *page = hotPage(); // 获取最新的page(即链表上最新的节点) if (page && !page->full()) { return page->add(obj); // 在这个page存在且不满的情况下,直接将需要autorelease的对象加入栈中 } else if (page) { return autoreleaseFullPage(obj, page); // 在这个page已经满了的情况下,新建一个page并将obj对象放入新的page(即入栈) } else { return autoreleaseNoPage(obj); // 在没有page的情况下,新建一个page并将obj对象放入新的page(即入栈) } }

autoreleaseFullPage(obj, page)autoreleaseNoPage(obj)的区别在于autoreleaseFullPage(obj, page)会将当前page的child指向新建的page,而autoreleaseNoPage(obj)会在新建的page中先入栈一个POOL_SENTINEL(哨兵对象),再将obj入栈。
id *add(id obj)// 入栈操作 { assert(!full()); unprotect(); // 解除保护 id *ret = next; // faster than `return next-1` because of aliasing *next++ = obj; // 将obj入栈到栈顶并重新定位栈顶 protect(); // 添加保护 return ret; }

AutoreleasePoolPage::pop(ctxt)
static inline void pop(void *token)// token指针指向栈顶的地址 { AutoreleasePoolPage *page; id *stop; page = pageForPointer(token); // 通过栈顶的地址找到对应的page stop = (id *)token; if (DebugPoolAllocation&&*stop != POOL_SENTINEL) { // This check is not valid with DebugPoolAllocation off // after an autorelease with a pool page but no pool in place. _objc_fatal("invalid or prematurely-freed autorelease pool %p; ", token); } if (PrintPoolHiwat) printHiwat(); // 记录最高水位标记page->releaseUntil(stop); // 从栈顶开始操作出栈,并向栈中的对象发送release消息,直到遇到第一个哨兵对象 // memory: delete empty children // 删除空掉的节点 if (DebugPoolAllocation&&page->empty()) { // special case: delete everything during page-per-pool debugging AutoreleasePoolPage *parent = page->parent; page->kill(); setHotPage(parent); } else if (DebugMissingPools&&page->empty()&&!page->parent) { // special case: delete everything for pop(top) // when debugging missing autorelease pools page->kill(); setHotPage(nil); } else if (page->child) { // hysteresis: keep one empty child if page is more than half full if (page->lessThanHalfFull()) { page->child->kill(); } else if (page->child->child) { page->child->child->kill(); } } }

AutoreleasePoolPage::autorelease((id)this);
static inline id autorelease(id obj) { assert(obj); assert(!obj->isTaggedPointer()); id *dest __unused = autoreleaseFast(obj); //添加obj对象到自动释放池的链表栈中 assert(!dest||*dest == obj); return obj; }

autorelease函数和push函数一样,关键代码都是调用autoreleaseFast函数向自动释放池的链表栈中添加一个对象,不过push函数的入栈的是一个哨兵对象,而autorelease函数入栈的是需要加入autoreleasepool的对象。
六、补充
使用容器的block版本的枚举器时,内部会自动添加一个AutoreleasePool:
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { // 这里被一个局部@autoreleasepool包围着 }];

普通for循环和for in循环中没有,for循环中遍历产生大量autorelease变量时,就需要手加局部AutoreleasePool。
  • 下面三种情况下是需要我们手动添加 autoreleasepool 的:
【Autorelease|Autorelease Pool 知识汇总】1、你编写的程序不是基于 UI 框架的,比如说命令行工具;
2、你编写的循环中创建了大量的临时对象;
3、你创建了一个辅助线程。
参考链接 http://blog.sunnyxx.com/2014/10/15/behind-autorelease/
http://blog.leichunfeng.com/blog/2015/05/31/objective-c-autorelease-pool-implementation-principle/#jtss-tsina

    推荐阅读