Autorelease

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加入

AutoreleasePoolPage.png

释放时刻

每当进行一次objc_autoreleasePoolPush调用时,runtime向当前的AutoreleasePoolPage中add进一个哨兵对象,值为0(也就是个nil),那么这一个page就变成了下面的样子:

image

objc_autoreleasePoolPush的返回值正是这个哨兵对象的地址,被objc_autoreleasePoolPop(哨兵对象)作为入参,于是:

  1. 根据传入的哨兵对象地址找到哨兵对象所处的page
  2. 在当前page中,将晚于哨兵对象插入的所有autorelease对象都发送一次- release消息,并向回移动next指针到正确位置
  3. 补充2:从最新加入的对象一直向前清理,可以向前跨越若干个page,直到哨兵所在的page

刚才的objc_autoreleasePoolPop执行后,最终变成了下面的样子:

image

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

参考地址
http://blog.sunnyxx.com/2014/10/15/behind-autorelease/

你可能感兴趣的:(Autorelease)