前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >浅谈 AutoreleasePool 的实现原理

浅谈 AutoreleasePool 的实现原理

作者头像
s_在路上
发布2018-09-30 10:55:35
1.4K0
发布2018-09-30 10:55:35
举报
文章被收录于专栏:iOS 开发杂谈iOS 开发杂谈

面试题:

autorelease 对象什么时候释放。

autorelease 的本质就是延迟调用 release 方法

MRC 的环境下,可以通过调用 [obj autorelease] 将对象添加到当前的 autoreleasepool 中,来延迟释放内存;

ARC的环境下,当我们创建一个对象,可以通过 __autoreleasing 修饰符,会将对象添加到当前的 autoreleasepool 中,当 autoreleasepool 销毁时,会对 autoreleasepool 里面的所有对象做一次 release 操作。

注意:

  • 编译器会检查方法名是否以 allocnewcopymutableCopy 开始,如果不是则自动将返回值的对象注册到 autoreleasepool 中;
  • __weak 修饰的对象,会注册到 autoreleasepool 中。
  • 调用 Foundation 对象的类方法(比如,[NSMutableDictionary dictionary][NSArray array] 等)会注册到 autoreleasepool 中。
  • id 的指针或对象的指针在没有显式地指定修饰符时候,会被默认附加上 __autoreleasing 修饰符。

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

autoreleasepool 销毁时,在调用堆栈中可以发现,系统调用了 -[NSAutoreleasePool release] 方法,这个方法最终通过调用 AutoreleasePoolPage::pop(void *) 函数来负责对 autoreleasepool 中的 autorelease 对象执行 release 操作。

AutoreleasePool 的实现原理

代码语言:javascript
复制
int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

@autoreleasepool

使用 clang -rewrite-objc 命令将下面的 Objective-C 代码重写成 C++ 代码:

代码语言:javascript
复制
clang -rewrite-objc main.m

extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);
struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};
#define __OFFSETOFIVAR__(TYPE, MEMBER) ((long long) &((TYPE *)0)->MEMBER)
int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
    }
}

声明一个 __AtAutoreleasePool 类型的局部变量 __autoreleasepool 来实现 @autoreleasepool {}。当声明 __autoreleasepool 变量时,构造函数 __AtAutoreleasePool() 被调用,即执行:

代码语言:javascript
复制
atautoreleasepoolobj = objc_autoreleasePoolPush();

当出了当前作用域时,析构函数 ~__AtAutoreleasePool() 被调用,即执行:

代码语言:javascript
复制
objc_autoreleasePoolPop(atautoreleasepoolobj);

也就是说 @autoreleasepool {} 的实现代码可以进一步简化如下:

代码语言:javascript
复制
/* @autoreleasepool */ {
    void *atautoreleasepoolobj = objc_autoreleasePoolPush();
    // 用户代码,所有接收到 autorelease 消息的对象会被添加到这个 autoreleasepool 中
    objc_autoreleasePoolPop(atautoreleasepoolobj);
}

因此,单个 autoreleasepool 的运行过程可以简单地理解为 objc_autoreleasePoolPush()[obj release]objc_autoreleasePoolPop(void *) 三个过程。

AutoreleasePoolPage

image.png

从图中可以看出

  • AutoreleasePoolPage 是由双向链表来实现的,parentchild 就是用来构造双向链表的指针。
  • magic 用来校验 AutoreleasePoolPage 的结构是否完整;
  • AutoreleasePool 是按线程一一对应的,结构中的 thread 指针指向当前线程。
  • AutoreleasePoolPage 会为每个对象会开辟 4096 字节内存。
  • id *next 指向了下一个为空的内存地址(初始化为栈底),如果有添加进来的 autorelease 对象,移动到下一个为空的内存地址中。

如果 AutoreleasePoolPage 里面的 autorelease 对象满了,也就是 id *next 指针指向了栈顶,会新建一个 AutoreleasePoolPage 对象,连接链表,后来添加的 autorelease 对象在新的 AutoreleasePoolPage 加入,id *next 指针指向新的 AutoreleasePoolPage 为空的内存地址,即栈底。所以,向一个对象发送 release 消息,就是将这个对象加入到当前 AutoreleasePoolPageid *next 指针指向的位置。

POOL_SENTINEL(哨兵对象)

image.png

POOL_SENTINEL 只是 nil 的别名。

在每个自动释放池初始化调用 objc_autoreleasePoolPush 的时候,都会把一个 POOL_SENTINEL push 到自动释放池的栈顶,并且返回这个 POOL_SENTINEL 哨兵对象。

而当方法 objc_autoreleasePoolPop 调用时,就会向自动释放池中的对象发送 release 消息,直到第一个 POOL_SENTINEL

详细参考POOL_SENTINEL(哨兵对象)

objc_autoreleasePoolPush

objc_autoreleasePoolPush() 函数本质上就是调用的 AutoreleasePoolPagepush 函数。

代码语言:javascript
复制
void * objc_autoreleasePoolPush(void) {
    if (UseGC) return nil;
    return AutoreleasePoolPage::push();
}

根据源码得出,每次执行 objc_autoreleasePoolPush 其实就是创建了一个新的 autoreleasepool,然后会把一个 POOL_SENTINEL push 到自动释放池的栈顶,并且返回这个 POOL_SENTINEL 哨兵对象。

代码语言:javascript
复制
static inline void *push() {
    id *dest = autoreleaseFast(POOL_SENTINEL);
    assert(*dest == POOL_SENTINEL);
    return dest;
}

push 函数通过调用 autoreleaseFast 函数并传入哨兵对象 POOL_SENTINEL 来执行具体的插入操作。

代码语言:javascript
复制
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);
    }
}

id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {
    // The hot page is full.
    // Step to the next non-full page, adding a new page if necessary.
    // Then add the object to that page.
    assert(page == hotPage());
    assert(page->full()  ||  DebugPoolAllocation);
    
    do {
        if (page->child) page = page->child;
        else page = new AutoreleasePoolPage(page);
    } while (page->full());
    
    setHotPage(page);
    return page->add(obj);
}

id *autoreleaseNoPage(id obj) {
    // No pool in place.
    assert(!hotPage());
    
    if (obj != POOL_SENTINEL  &&  DebugMissingPools) {
        // We are pushing an object with no pool in place,
        // and no-pool debugging was requested by environment.
        _objc_inform("MISSING POOLS: Object %p of class %s "
                     "autoreleased with no pool in place - "
                     "just leaking - break on "
                     "objc_autoreleaseNoPool() to debug",
                     (void*)obj, object_getClassName(obj));
        objc_autoreleaseNoPool(obj);
        return nil;
    }
    
    // Install the first page.
    AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
    setHotPage(page);
    
    // Push an autorelease pool boundary if it wasn't already requested.
    if (obj != POOL_SENTINEL) {
        page->add(POOL_SENTINEL);
    }
    
    // Push the requested object.
    return page->add(obj);
}

autoreleaseFast 函数在执行一个具体的插入操作时,分别对三种情况进行了不同的处理:

  • 当前 hotPage 存在且没有满时,调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中。
  • 当前 hotPage 存在且已满时,调用 autoreleaseFullPage 初始化一个新的 page,调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中。
  • 当前 hotPage 不存在时,调用 autoreleaseNoPage 创建一个 hotPage,调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中。

objc_autoreleasePoolPop

objc_autoreleasePoolPop(void *)函数本质上也是调用的AutoreleasePoolPage的pop函数。

代码语言:javascript
复制
void objc_autoreleasePoolPop(void *ctxt) {
    if (UseGC) return;
    // fixme rdar://9167170
    if (!ctxt) return;
    AutoreleasePoolPage::pop(ctxt);
}

static inline void pop(void *token) {
    AutoreleasePoolPage *page = pageForPointer(token);
    id *stop = (id *)token;

    page->releaseUntil(stop);

    if (page->child) {
        if (page->lessThanHalfFull()) {
            page->child->kill();
        } else if (page->child->child) {
            page->child->child->kill();
        }
    }
}

pop 函数的入参就是 push 函数的返回值,也就是POOL_SENTINEL 的内存地址。根据这个内存地址找到所在的 AutoreleasePoolPage 然后使用 objc_release 释放 POOL_SENTINEL 指针之前的对象。

总结: 每调用一次 push 操作就会创建一个新的 autoreleasepool,然后往 AutoreleasePoolPage 中插入一个 POOL_SENTINEL,并且返回插入的 POOL_SENTINEL 的内存地址. 在执行 pop 操作的时候传入 POOL_SENTINEL,根据传入的哨兵对象地址找到哨兵对象所处的 page 在当前AutoreleasePoolPage中,然后使用 objc_release 释放 POOL_SENTINEL 指针之前的对象,并把 id next 指针到正确位置。

参考

自动释放池的前世今生 ---- 深入解析 Autoreleasepool

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.09.13 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 面试题:
  • AutoreleasePool 的实现原理
    • @autoreleasepool
      • AutoreleasePoolPage
        • POOL_SENTINEL(哨兵对象)
          • objc_autoreleasePoolPush
            • objc_autoreleasePoolPop
            • 参考
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档