美文网首页
浅析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