美文网首页
浅析autoreleasePool

浅析autoreleasePool

作者: 水水兔 | 来源:发表于2019-07-11 15:46 被阅读0次

<font color = 'gray'>2018-10-26 编辑 :yzl </font>

Autorelease对象什么时候释放?

在没有手加Autorelease Pool的情况下,Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop。

实验

__weak id reference = nil;
- (void)viewDidLoad {
    [super viewDidLoad];
    NSString *str = [NSString stringWithFormat:@"sunnyxx"];
    // str是一个autorelease对象,设置一个weak的引用来观察它
    reference = str;
}
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    NSLog(@"%@", reference); // Console: sunnyxx
}
- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    NSLog(@"%@", reference); // Console: (null)
}

这个实验同时也证明了viewDidLoad和viewWillAppear是在同一个runloop调用的,而viewDidAppear是在之后的某个runloop调用的。
由于这个vc在loadView之后便add到了window层级上,所以viewDidLoad和viewWillAppear是在同一个runloop调用的,因此在viewWillAppear中,这个autorelease的变量依然有值。

当然,我们也可以手动干预Autorelease对象的释放时机:

- (void)viewDidLoad {
    [super viewDidLoad];
    @autoreleasepool {
        NSString *str = [NSString stringWithFormat:@"sunnyxx"];
    }
    NSLog(@"%@", str); // Console: (null)
}

Autorelease原理

int main(int argc, char * argv[]) {
    @autoreleasepool {

    }
}

clang之后

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;


    }
}

也就是说 @autoreleasepool {} 被转换为一个 __AtAutoreleasePool 结构体:

struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};

这个结构体会在初始化时调用 objc_autoreleasePoolPush() 方法,会在析构时调用 objc_autoreleasePoolPop 方法。

这表明,我们的 main 函数在实际工作时其实是这样的:

void *context = objc_autoreleasePoolPush();
// {}中的代码
objc_autoreleasePoolPop(context);

objc_autoreleasePoolPush 和 objc_autoreleasePoolPop 的实现:

void *objc_autoreleasePoolPush(void) {
    return AutoreleasePoolPage::push();
}

void objc_autoreleasePoolPop(void *ctxt) {
    AutoreleasePoolPage::pop(ctxt);
}

而这两个函数都是对AutoreleasePoolPage的简单封装,所以自动释放机制的核心就在于这个类。

AutoreleasePoolPage是一个C++实现的类


51530583gw1elj2ugt21wj20f109m3zl.jpg
  • AutoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage以双向链表的形式组合而成(分别对应结构中的parent指针和child指针)
1975281-0b868956aa0cf87c.png
  • AutoreleasePool是按线程一一对应的(结构中的thread指针指向当前线程)
  • AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址
  • 上面的id *next指针作为游标指向栈顶最新add进来的autorelease对象的下一个位置
  • 一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的page加入

所以,若当前线程中只有一个AutoreleasePoolPage对象,并记录了很多autorelease对象地址时内存如下图:


51530583gw1elj5gvphtqj20dy0cx756.jpg

图中的情况,这一页再加入一个autorelease对象就要满了(也就是next指针马上指向栈顶),这时就要执行上面说的操作,建立下一页page对象,与这一页链表连接完成后,新page的next指针被初始化在栈底(begin的位置),然后继续向栈顶添加新对象。

所以,向一个对象发送- autorelease消息,就是将这个对象加入到当前AutoreleasePoolPage的栈顶next指针指向的位置。

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


51530583gw1elj5z7hawej20ji0dewff.jpg

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

根据传入的哨兵对象地址找到哨兵对象所处的page
在当前page中,将晚于哨兵对象插入的所有autorelease对象都发送一次- release消息,并向回移动next指针到正确位置
补充2:从最新加入的对象一直向前清理,可以向前跨越若干个page,直到哨兵所在的page
刚才的objc_autoreleasePoolPop执行后,最终变成了下面的样子:

51530583gw1elj6u2i3fyj20dz0bqdgi.jpg

知道了上面的原理,嵌套的AutoreleasePool就非常简单了,pop的时候总会释放到上次push的位置为止,多层的pool就是多个哨兵对象而已,就像剥洋葱一样,每次一层,互不影响。

objc_autoreleasePoolPush

objc_autoreleasePoolPush 方法:

void *objc_autoreleasePoolPush(void) {
    return AutoreleasePoolPage::push();
}

它调用 AutoreleasePoolPage 的类方法 push,也非常简单:

static inline void *push() {
   return autoreleaseFast(POOL_SENTINEL);
}

在这里会进入一个比较关键的方法 autoreleaseFast,并传入哨兵对象 POOL_SENTINEL:

static inline id *autoreleaseFast(id obj)
{
   AutoreleasePoolPage *page = hotPage();
   if (page && !page->full()) {
       return page->add(obj);
   } else if (page) {
       return autoreleaseFullPage(obj, page);
   } else {
       return autoreleaseNoPage(obj);
   }
}

上述方法分三种情况选择不同的代码执行:

  • 有 hotPage 并且当前 page 不满
  • 调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中
  • 有 hotPage 并且当前 page 已满
  • 调用 autoreleaseFullPage 初始化一个新的页
  • 调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中
  • 无 hotPage
  • 调用 autoreleaseNoPage 创建一个 hotPage
  • 调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中
  • 最后的都会调用 page->add(obj) 将对象添加到自动释放池中。

当obj 调用autoRelease方法时,其实就是调用autoreleaseFast(obj);

- [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)

结束

本文暂时分析到此,后续有新认识,再添加。。。。

参考文献

相关文章

网友评论

      本文标题:浅析autoreleasePool

      本文链接:https://www.haomeiwen.com/subject/vejdkctx.html