美文网首页ios
苹果的AutoreleasePool

苹果的AutoreleasePool

作者: 羽裳有涯 | 来源:发表于2019-01-18 14:24 被阅读11次

    AutoreleasePool

    App启动后,苹果在主线程RunLoop 里注册了两个Observer,其回调都是_wrapRunLoopWithAutoreleasePoolHandler()

    第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用_objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。

    第二个 Observer 监视了两个事件:BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop()_objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用_objc_autoreleasePoolPop() 来释放自动释放池。这个 Observerorder 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。

    在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop创建好的AutoreleasePool环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。

    从 main 函数开始

    main 函数可以说是在整个 iOS 开发中非常不起眼的一个函数,它很好地隐藏在Supporting Files 文件夹中,却是整个 iOS 应用的入口。
    main.m 文件中的内容是这样的:

    int main(int argc, char * argv[]) {
        @autoreleasepool {
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
    }
    

    在这个 @autoreleasepool block 中只包含了一行代码,这行代码将所有的事件、消息全部交给了UIApplication来处理,但是这不是本文关注的重点。
    需要注意的是:整个 iOS 的应用都是包含在一个自动释放池 block 中的。

    @autoreleasepool

    @autoreleasepool 到底是什么?我们在命令行中使用clang -rewrite-objc main.m让编译器重新改写这个文件:

    $ clang -rewrite-objc main.m
    

    找到 main.cpp 文件
    在这个文件中,有一个非常奇怪的__AtAutoreleasePool的结构体,前面的注释写到 /* @autoreleasepopl */。也就是说 @autoreleasepool {} 被转换为一个 __AtAutoreleasePool结构体:

    {
        __AtAutoreleasePool __autoreleasepool;
    }
    

    想要弄清楚这行代码的意义,我们要在main.cpp中查找名为 __AtAutoreleasePool的结构体:

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

    这个结构体会在初始化时调用 objc_autoreleasePoolPush()方法,会在析构时调用 objc_autoreleasePoolPop方法。
    这表明,我们的 main 函数在实际工作时其实是这样的:

    int main(int argc, const char * argv[]) {
        {
            void * atautoreleasepoolobj = objc_autoreleasePoolPush();
    
            // do whatever you want
    
            objc_autoreleasePoolPop(atautoreleasepoolobj);
        }
        return 0;
    }
    

    @autoreleasepool只是帮助我们少写了这两行代码而已,让代码看起来更美观,然后要根据上述两个方法来分析自动释放池的实现。

    AutoreleasePool 是什么

    这一节开始分析方法 objc_autoreleasePoolPushobjc_autoreleasePoolPop 的实现:

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

    上面的方法看上去是对 AutoreleasePoolPage 对应静态方法 pushpop 的封装。

    这一小节会按照下面的顺序逐步解析代码中的内容:

    AutoreleasePoolPage 的结构

    AutoreleasePoolPage 是一个 C++ 中的类:
    它在 NSObject.mm 中的定义是这样的:

    class AutoreleasePoolPage {
        magic_t const magic;
        id *next;
        pthread_t const thread;
        AutoreleasePoolPage * const parent;
        AutoreleasePoolPage *child;
        uint32_t const depth;
        uint32_t hiwat;
    };
    

    magic用于对当前AutoreleasePoolPage 完整性的校验
    thread保存了当前页所在的线程
    每一个自动释放池都是由一系列的 AutoreleasePoolPage组成的,并且每一个 AutoreleasePoolPage 的大小都是 4096 字节(16 进制 0x1000)

    #define I386_PGBYTES 4096
    #define PAGE_SIZE I386_PGBYTES
    

    双向链表

    自动释放池中的 AutoreleasePoolPage 是以双向链表的形式连接起来的:


    图片.png

    parent 和 child 就是用来构造双向链表的指针。

    releaseUntil 释放对象

    releaseUntil 方法的实现如下:

    void releaseUntil(id *stop) {
        while (this->next != stop) {
            AutoreleasePoolPage *page = hotPage();
    
            while (page->empty()) {
                page = page->parent;
                setHotPage(page);
            }
    
            page->unprotect();
            id obj = *--page->next;
            memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
            page->protect();
    
            if (obj != POOL_SENTINEL) {
                objc_release(obj);
            }
        }
    
        setHotPage(this);
    }
    

    它的实现还是很容易的,用一个 while 循环持续释放 AutoreleasePoolPage 中的内容,直到next指向了 stop 。
    使用 memset 将内存的内容设置成SCRIBBLE,然后使用 objc_release 释放对象。

    kill() 方法

    会将当前页面以及子页面全部删除:

    void kill() {
        AutoreleasePoolPage *page = this;
        while (page->child) page = page->child;
    
        AutoreleasePoolPage *deathptr;
        do {
            deathptr = page;
            page = page->parent;
            if (page) {
                page->unprotect();
                page->child = nil;
                page->protect();
            }
            delete deathptr;
        } while (deathptr != this);
    }
    

    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 方法:

    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) {
       id *dest __unused = autoreleaseFast(obj);
       return obj;
    }
    

    小结

    • 自动释放池是由 AutoreleasePoolPage 以双向链表的方式实现的
    • 当对象调用autorelease方法时,会将对象加入AutoreleasePoolPage 的栈中
    • 调用AutoreleasePoolPage::pop 方法会向栈中的对象发送 release 消息

    相关文章

      网友评论

        本文标题:苹果的AutoreleasePool

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