美文网首页
Autoreleasepool它到底长什么样?

Autoreleasepool它到底长什么样?

作者: DL是谁 | 来源:发表于2019-08-28 15:18 被阅读0次
    封面

    来,来,来看看这个平时不用,其实它一直那里的Autoreleasepool长什么样~
    其实在main.m 文件入口就已经给我们加好了@autoreleasepool的内容是这样的:

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

    所以整个 iOS 的应用都是包含在一个自动释放池 block 中的。

    实际工作时其实是这样的:

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

    objc_autoreleasePoolPushobjc_autoreleasePoolPop又是啥呢?

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

    就是AutoreleasePoolPage的两个pushpop方法,接下来我们这三个一个个看到底都是些啥玩意儿。

    AutoreleasePoolPage

    先看看 AutoreleasePoolPage 是个啥玩意儿呢?
    其实,autoreleasepool 是没有单独的内存结构的,每一个自动释放池都是由一系列的 AutoreleasePoolPage 组成的,并且每一个AutoreleasePoolPage 的大小都是 4096 字节(16 进制 0x1000)

    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结构是否完整;
    • next指向第一个可用的地址;
    • thread指向当前的线程;
    • parent指向父类
    • child指向子类
    autorelease双向链表

    Push

    static inline void *push()
    {
        //autoreleaseFast 关键
        id *dest = autoreleaseFast(POOL_SENTINEL);
        assert(*dest == POOL_SENTINEL);
        return dest;
    }
    

    再看autoreleaseFast执行具体的插入操作

    static inline id *autoreleaseFast(id obj)
    {
        //hotPage 可以理解为当前正在使用的 AutoreleasePoolPage
        AutoreleasePoolPage *page = hotPage();
        if (page && !page->full()) {
        //当前 page 存在且没有满时,直接将对象添加到当前 page 中
            return page->add(obj);
        } else if (page) {
        //当前 page 存在且已满时,创建一个新的 page ,并将对象添加到新创建的 page 中
            return autoreleaseFullPage(obj, page);
        } else {
        //当前 page 不存在时,即还没有 page 时,创建第一个 page ,并将对象添加到新创建的 page 中
            return autoreleaseNoPage(obj);
        }
    }
    
    page->add 添加对象
    id *add(id obj) {
        id *ret = next;
        *next = obj;
        next++;
        return ret;
    }
    

    这个方法其实就是一个压栈的操作,将对象加入 AutoreleasePoolPage 然后移动栈顶的指针

    autoreleaseFullPage(当前 hotPage 已满)
    static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {
        do {
            if (page->child) page = page->child;
            else page = new AutoreleasePoolPage(page);
        } while (page->full());
     
        setHotPage(page);
        return page->add(obj);
    }
    

    它会从传入的 page 开始先遍历整个双向链表

    • 一直遍历,直到找到一个未满的AutoreleasePoolPage
    • 如果找到最后还没找到,就新建一个 AutoreleasePoolPage
    • 将该页面标记成 hotPage
    • 调动 page->add方法添加对象。
    autoreleaseNoPage(没有 hotPage)
    static id *autoreleaseNoPage(id obj) {
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        setHotPage(page);
        if (obj != POOL_SENTINEL) {
            page->add(POOL_SENTINEL);
        }
        return page->add(obj);
    }
    

    POOL_SENTINEL是一个边界对象 nil,用来区别每个page即每个 AutoreleasePoolPage边界

    如果当前内存中不存在 AutoreleasePoolPage,就要构建一个新的自动释放池的双向链表,将当前页标记为 hotPage。但是第一个 AutoreleasePoolPage 是没有parent 指针的,所以会先向这个page中添加一个POOL_SENTINEL 对象,来确保在pop调用的时候,不会出现异常。

    push小结

    一个 push 操作其实就是创建一个新的 autoreleasepool ,对应 AutoreleasePoolPage 的具体实现就是往 AutoreleasePoolPage 中的 next 位置插入一个 POOL_SENTINEL,并且返回插入的POOL_SENTINEL 的内存地址。

    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)
    

    autoreleasepush 操作的实现非常相似。只不过 push 操作插入的是一个POOL_SENTINEL ,而 autorelease 操作插入的是一个具体的autoreleased对象。

    Pop

    先放一张图:

    Pop.png 其实pop在内存中的变化就是长这个样子。将边界对象指向的这一页 AutoreleasePoolPage 内的对象释放

    回头看一看 objc_autoreleasePoolPop

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

    pop 函数的入参就是 push 函数的返回值,也就是 POOL_SENTINEL 的内存地址,即pool token 。当执行pop 操作时,内存地址在 pool token之后的所有autoreleased对象都会被 release。直到pool token所在 page 的 next 指向 pool token为止。

    static inline void pop(void *token) {
    //获取当前 token 所在的 AutoreleasePoolPage
        AutoreleasePoolPage *page = pageForPointer(token);
        id *stop = (id *)token;
    //释放栈中的对象,直到 stop
        page->releaseUntil(stop);
        if (page->child) {
            if (page->lessThanHalfFull()) {
                page->child->kill();
            } else if (page->child->child) {
                page->child->child->kill();
            }
        }
    }
    
    • pageForPointer获取当前 token 所在的 AutoreleasePoolPage
      -- 通过内存地址的操作,获取当前指针所在页的首地址
    • releaseUntil 方法释放栈中的对象,直到 stop
      -- 用循环持续释放 AutoreleasePoolPage 中的内容(objc_release ),直到 next 指向了 stop
    • childkill 方法
      --它会将当前页面以及子页面全部删除

    总结

    AutoreleasePool = AutoreleasePoolPage (4096字节) * n;
    AutoreleasePoolPage = push + autorelease+pop;
    pushautorelease最终都是调用 autoreleaseFast方法,变了花的往next位置插POOL_SENTINEL或对象
    pop 传入边界对象,然后对page 中的对象发送release 的消息

    其实

    通常情况下,我们是不需要手动添加 autoreleasepool 的,使用线程自动维护的 autoreleasepool 就好了。根据苹果官方文档中对 Using Autorelease Pool Blocks 的描述,我们知道在下面三种情况下是需要我们手动添加 autoreleasepool 的:

    如果你编写的程序不是基于 UI 框架的,比如说命令行工具;
    如果你编写的循环中创建了大量的临时对象;
    如果你创建了一个辅助线程。

    参考资料

    What is autoreleasepool? [duplicate]
    NSAutoreleasePool
    iOS之autoreleasepool详解
    自动释放池的前世今生 ---- 深入解析 Autoreleasepool
    各个线程 Autorelease 对象的内存管理

    相关文章

      网友评论

          本文标题:Autoreleasepool它到底长什么样?

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