Autorelease介绍
Autorelease机制是iOS开发者管理对象内存的好伙伴,MRC中,调用[obj autorelease]来延迟内存的释放是一件简单自然的事,ARC下,我们甚至可以完全不知道Autorelease就能管理好内存。而在这背后,objc和编译器都帮我们做了哪些事呢,它们是如何协作来正确管理内存的呢?
Autorelease的使用
// 创建一个自动释放池
@autoreleasepool {
Person *person = [[[Person alloc] init] autorelease];
}
// 转成C++的代码如下
{ __AtAutoreleasePool __autoreleasepool;
Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
}
// __AtAutoreleasePool结构体
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
// 哨兵对象
void * atautoreleasepoolobj;
};
// 简化C++代码 如下
atautoreleasepoolobj = objc_autoreleasePoolPush();
Person *person = [[[Person alloc] init] autorelease];
objc_autoreleasePoolPop(atautoreleasepoolobj);
那么问题来了这三行代码做了什么,让我们来看一下runtime的源码实现
// 先来看一下第一行代码
atautoreleasepoolobj = objc_autoreleasePoolPush();
// runtime实现
void *objc_autoreleasePoolPush(void)
{
// 那AutoreleasePoolPage这个东东是个什么玩意儿里 我们稍后说
return AutoreleasePoolPage::push();
}
// 来看一下二行,第二行简单的说就是Person实例对象调用了一下Autorelease方法,来看一下Autorelease方法
- (id)autorelease {
return ((id)self)->rootAutorelease();
}
// 经过连续调用最终来到
__attribute__((noinline,used)) id objc_object::rootAutorelease2()
{
// 判断是否是TaggedPointer 这个是对NSNumber 、NSString、NSDate等对象的内存优化
assert(!isTaggedPointer());
// 把当前对象传进去 。又见到了AutoreleasePoolPage这个东东
return AutoreleasePoolPage::autorelease((id)this);
}
// 再看一下第三行
void objc_autoreleasePoolPop(void *ctxt)
{
// 又是AutoreleasePoolPage这个东东
AutoreleasePoolPage::pop(ctxt);
}
AutoreleasePoolPage这个是个什么东西呢?
// 一个C++实现类
class AutoreleasePoolPage {
// 省略部分代码
static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
PAGE_MAX_SIZE; // must be multiple of vm page size
#else
PAGE_MAX_SIZE; // size and alignment, power of 2
#endif
static size_t const COUNT = SIZE / sizeof(id); // 大小 4096字节
magic_t const magic;
id *next; // 最后一个Autorelease对象
pthread_t const thread; // 对应的线程
AutoreleasePoolPage * const parent; // 上一页(AutoreleasePoolPag对象)
AutoreleasePoolPage *child; // 下一页(AutoreleasePoolPag对象)
uint32_t const depth;
uint32_t hiwat;
// 省略部分代码
}
// AutoreleasePoolPage
1. AutoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage以双向链表的形式组合而成(分别对应结构中的parent指针和child指针)
2. AutoreleasePool是按线程一一对应的(结构中的thread指针指向当前线程)
3. AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址
4. 上面的id *next指针作为游标指向栈顶最新add进来的autorelease对象的下一个位置
5. 一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的page加入
释放时刻
每当进行一次objc_autoreleasePoolPush
调用时,runtime向当前的AutoreleasePoolPage中add进一个哨兵对象
,值为0(也就是个nil),那么这一个page就变成了下面的样子:
objc_autoreleasePoolPush
的返回值正是这个哨兵对象的地址,被objc_autoreleasePoolPop(哨兵对象)
作为入参,于是:
- 根据传入的哨兵对象地址找到哨兵对象所处的page
- 在当前page中,将晚于哨兵对象插入的所有autorelease对象都发送一次
- release
消息,并向回移动next
指针到正确位置 - 补充2:从最新加入的对象一直向前清理,可以向前跨越若干个page,直到哨兵所在的page
刚才的objc_autoreleasePoolPop执行后,最终变成了下面的样子:
Runloop和Autorelease
iOS在主线程的Runloop中注册了2个Observer
第1个Observer监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()
第2个Observer监听了kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()
Autorelease什么时候释放
1.在没有手加Autorelease Pool的情况下,Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop
2.有手动加Autorelease Pool的情况下出了作用域就会调用自动释放池Pop对每个对象调用release