概念
AutoreleasePool(自动释放池)是OC中的一种内存自动回收机制,它可以延迟加入AutoreleasePool中的变量release的时机。在正常情况下,创建的变量会在超出其作用域的时候release,但是如果将变量加入AutoreleasePool,那么release将延迟执行。
AutoreleasePool的创建和释放
创建
- App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是
_wrapRunLoopWithAutoreleasePoolHandler()。 - 第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用
_objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
释放
- 第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠)
时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush()
释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个
Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。 - 在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。
也就是说AutoreleasePool创建是在一个RunLoop事件开始之前(push),
AutoreleasePool释放是在一个RunLoop事件即将结束之前(pop)。
AutoreleasePool里的Autorelease对象的加入是在RunLoop事件中,
AutoreleasePool里的Autorelease对象的释放是在AutoreleasePool释放时。
main.m 文件里面的@autoreleasepool
使用clang将OC代码转为C++
cd 到main.m文件夹下
1.cd /Users/zhaomiaomiao/Desktop/循环引用/循环引用
clang命令
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m
如图文件夹多了.cpp 文件
对比 main.cpp 与main.m 的main方法
可以发现多了__AtAutoreleasePool __autoreleasepool 这一行代码
AtAutoreleasePool的本质
struct __AtAutoreleasePool {
__AtAutoreleasePool() { // 构造函数,在创建结构体的时候调用
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
~__AtAutoreleasePool() { // 析构函数,在结构体销毁的时候调用
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
可以发现它是一个结构体
@autoreleasepool { }
{ } 是作用域,
{ } 里会调用构造函数间接的会调用objc_autoreleasePoolPush函数:
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
{ } 外调用 析构函数调用objc_autoreleasePoolPop函数
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
至此,我们可以分析出,单个自动释放池的执行过程就是*objc_autoreleasePoolPush() —> [object autorelease] —> objc_autoreleasePoolPop(void )。
Autoreleasepool源码
libobjc.A.dylib`objc_autoreleasePoolPush:
自动释放池是由多个autorelease page组成的双向链表,其中主要通过push及pop操作来管理:如图所示:
官方解释
一个线程的autoreleasepool就是一个指针栈。
栈中存放的指针指向加入需要release的对象或者POOL_SENTINEL(哨兵对象,用于分隔autoreleasepool)。
栈中指向POOL_SENTINEL的指针就是autoreleasepool的一个标记。当autoreleasepool进行出栈操作,每一个比这个哨兵对象后进栈的对象都会release。
这个栈是由一个以page为节点双向链表组成,page根据需求进行增减。
autoreleasepool对应的线程存储了指向最新page(也就是最新添加autorelease对象的page)的指针。
AutoreleasePoolPage
看代码
void *objc_autoreleasePoolPush(void) {
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt) {
AutoreleasePoolPage::pop(ctxt);
}
上面的方法看上去是对 AutoreleasePoolPage 对应静态方法 push 和 pop 的封装。
下面分析一下AutoreleasePoolPage的实现,揭开AutoreleasePool的实现原理。
// 自动释放池 是一个类
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
// 用来校验AutoreleasePoolPage 的结构是否完整
magic_t const magic;
// 指向最新添加的autoreleased,对象的下一个位置,初始化时指向begin();
__unsafe_unretained id *next;
// 指向当前线程
pthread_t const thread;
// 指向父节点,第一个节点parent值为nil
AutoreleasePoolPage * const parent;
// 指向子节点,最后一个节点的child为nil
AutoreleasePoolPage *child;
// 代表深度。从0开始,往后递增1;
uint32_t const depth;
// 最大入栈数标记
uint32_t hiwat;
// 初始化
AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
: magic(), next(_next), thread(_thread),
parent(_parent), child(nil),
depth(_depth), hiwat(_hiwat)
{
}
};
可以看出我们的自动释放池也是一个类
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
parent和child就是用来构造双向链表的指针。parent指向前一个page, child指向下一个page。
一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的page加入。
push
push整体流程
每当自动释放池调用objc_autoreleasePoolPush时都会把边界对象放进栈顶,然后返回边界对象,用于释放。
atautoreleasepoolobj = objc_autoreleasePoolPush();
atautoreleasepoolobj就是返回的边界对象(POOL_BOUNDARY)
push源码
objc_autoreleasePoolPush代码
objc_autoreleasePoolPush(void)
{
// 返回的边界对象
return AutoreleasePoolPage::push();
}
:: : 一个函数名调用类方法
它调用AutoreleasePoolPage的类方法push:
AutoreleasePoolPage 的讲解在上方
AutoreleasePoolPage: push方法
关键的方法autoreleaseFast,并传入边界对象(POOL_BOUNDARY)
AutoreleasePoolPage:autoreleaseFast方法
上述方法分三种情况选择不同的代码执行:
- 有 hotPage 并且当前 page 不满,调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中
- 有 hotPage 并且当前 page 已满,调用 autoreleaseFullPage 初始化一个新的页,调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中
- 无 hotPage,调用 autoreleaseNoPage 创建一个 hotPage,调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中
最后的都会调用 page->add(obj) 将对象添加到自动释放池中。
hotPage 可以理解为当前正在使用的 AutoreleasePoolPage。
AutoreleasePoolPage:add方法
自动释放池会先调用 objc_autoreleasePoolPush 函数,这函数首先在当前 next 指向的位置存放一个 POOL_BOUNDARY,然后当向一个对象发送 autorelease 消息时,会在哨兵对象后面插入指向该对象的指针,之后把 next 指向刚插入的位置的下一个内存地址,
当这一页 page 快满时(即 next 即将指向栈顶——end() 位置),说明这一页 page 快满了。这时如果再加入一个对象,会先建立下一页 page,双向链表建立完成后,新的 page 的 next 指向该页的栈底——begin() 位置,之后继续向栈顶添加新的指针。
pop
pop 整体流程
自动释放池释放是传入 push 返回的边界对象,
objc_autoreleasePoolPop(atautoreleasepoolobj);
然后将边界对象指向的这一页 AutoreleasePoolPage 内的对象释放
atautoreleasepoolobj就是返回的边界对象(POOL_BOUNDARY)
pop源码
调用完前面说的objc_autoreleasePoolPush后,会返回一个POOL_BOUNDARY的地址,当对象要释放时,会调用objc_autoreleasePoolPop函数,将该POOL_BOUNDARY作为其入参,然后会执行如下操作:
static inline void pop(void *token) //token指针指向栈顶的地址
{
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
if (hotPage()) {
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
pop(coldPage()->begin());
} else {
// Pool was never used. Clear the placeholder.
setHotPage(nil);
}
return;
}
page = pageForPointer(token); //通过栈顶的地址找到对应的page
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {// 不是边界
if (stop == page->begin() && !page->parent) {
// Start of coldest page may correctly not be POOL_BOUNDARY:
// 1. top-level pool is popped, leaving the cold page in place
// 2. an object is autoreleased with no pool
} else {
// Error. For bincompat purposes this is not
// fatal in executables built with old SDKs.
return badPop(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();
}
}
}
- page->releaseUntil(stop),对栈顶(page->next)到stop地址(POOL_SENTINEL)之间的所有对象调用objc_release(),进行引用计数减1
- 清空page对象page->kill(),有两句注释
除非是pop(0)方式调用,这样会清理掉所有page对象;
否则,在当前page存放的对象大于一半时,会保留一个空的子page,
这样估计是为了可能马上需要新建page节省创建page的开销。
- 根据传入的POOL_BOUNDARY(push后得到的那个)找到其所在的page;
- 从 hotPage 的 next 指针开始往前查找,向找到的每个指针调用 memset 方法以擦除指针所占内存,
- 再调用 objc_release 方法释放该指针指向的对象,
- 直到前一步所找到的 page 的 POOL_BOUNDARY 为止(可往前跨越多个 page),并且在释放前,next 指针也会往回指向正确的位置。
- 当有嵌套的 autoreleasepool 时,会清除一层后再清除另一层,因为 pop 是会释放到上次 push
的位置为止,就像剥洋葱一样,每次一层,互不影响。
autorelease
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方法的调用栈中,最终都会调用上面提到的 autoreleaseFast方法,将当前对象加到AutoreleasePoolPage 中。
这一小节中这些方法的实现都非常容易,只是进行了一些参数上的检查,最终还要调用autoreleaseFast方法:
autorelease 源码
inline id objc_object::rootAutorelease() {
if (isTaggedPointer()) return (id)this;
if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
return rootAutorelease2();
}
__attribute__((noinline,used)) id objc_object::rootAutorelease2() {
return AutoreleasePoolPage::autorelease((id)this);
}
static inline id autorelease(id obj) {
// 添加obj对象到autoreleasepool的链表栈中
id *dest __unused = autoreleaseFast(obj);
return obj;
}
autorelease函数和push函数一样,关键代码都是调用autoreleaseFast函数向自动释放池的链表栈中添加一个对象,
不过push函数的入栈的是一个边界对象,而autorelease函数入栈的是需要加入autoreleasepool的对象。
hiwat
AutoreleasePool的内存结构是一个双向链表栈,会频繁的有入栈和出栈的操作,栈中存放的对象也会有增有减,hiwat就是为了记录入栈对象最多时候对象的个数。
autorelease 对象会在什么时候释放?
使用@autoreleasepool,会在大括号结束时释放
不使用 @autoreleasepool,这个会由系统自动释放,释放时机是在当前 runloop 结束时释放,因为系统会自动为每个
runloop 执行自动释放池的 push 和 pop 操作
关于内存管理的方法,目前来说,有三种:
C/C++的完全由程序员管理(paring new/malloc & delete/free);
Garbage Collection;
Reference Counting;
第一种比较原始;Cocoa Touch的Reference Counting对比Garbage Collection,有一个致命的弱点:无法释放循环引用的对象,所以要注意不要造成循环引用。
Runloop和Autorelease
iOS在主线程的Runloop中注册了2个Observer
- 第1个Observer监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()
- 第2个Observer
<1> 监听了kCFRunLoopBeforeWaiting事件,会调用 objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
<2> 监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()
什么对象会加入Autoreleasepool中
- 使用alloc、new、copy、mutableCopy的方法进行初始化时,由系统管理对象,在适当的位置release。
- 使用array会自动将返回值的对象注册到Autoreleasepool。
- __weak修饰的对象,为了保证在引用时不被废弃,会注册到Autoreleasepool中。
- id的指针或对象的指针,在没有显示指定时会被注册到Autoleasepool中。