iOS学习笔记【四】——Autoreleasepool

iOS学习笔记【四】——Autoreleasepool

只做简单笔记📝 详细请戳标题链接🔗

main函数入口

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

@autoreleasepool {} 被转换为一个 __AtAutoreleasePool 结构体

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

这个结构体会在初始化时调用 objc_autoreleasePoolPush() 方法,会在析构时调用 objc_autoreleasePoolPop() 方法,实际都调用了AutoreleasePoolPage的方法

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

AutoreleasePoolPage

  • 每一个自动释放池都是由一系列的 AutoreleasePoolPage 组成的,并且每一个 AutoreleasePoolPage 的大小都是 4096 字节。
  • 自动释放池中的 AutoreleasePoolPage 是以双向链表的形式连接起来的。
class AutoreleasePoolPage {
    magic_t const magic; //用于对当前 AutoreleasePoolPage 完整性的校验
    id *next; //指向了下一个为空的内存地址
    pthread_t const thread; //保存了当前页所在的线程
    AutoreleasePoolPage * const parent; //用来构造双向链表的指针
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
};

AutoreleasePoolPage的结构栈
在这里插入图片描述
其中有 56 bit 用于存储 AutoreleasePoolPage 的成员变量,剩下的空间都是用来存储加入到自动释放池中的对象,begin()end() 这两个类的实例方法帮助我们快速获取这一空间。

POOL_SENTINEL(哨兵对象)

#define POOL_SENTINEL nil // nil别名
  • 在每个自动释放池初始化调用 objc_autoreleasePoolPush() 的时候,都会把一个 POOL_SENTINEL push 到自动释放池的栈顶,并且返回这个 POOL_SENTINEL 哨兵对象。
  • 而当方法 objc_autoreleasePoolPop 调用时,就会向自动释放池中的对象发送 release 消息,直到第一个 POOL_SENTINEL。

objc_autoreleasePoolPush 方法

static inline void *push() {
   return autoreleaseFast(POOL_SENTINEL);
}
static inline id *autoreleaseFast(id obj) {
   AutoreleasePoolPage *page = hotPage(); // 当前正在使用的 AutoreleasePoolPage
   if (page && !page->full()) {
       return page->add(obj); // 将对象添加至 AutoreleasePoolPage 的栈中
   } else if (page) {
       return autoreleaseFullPage(obj, page); // 当前 hotPage 已满,查找下一未满page
   } else {
       return autoreleaseNoPage(obj); // 创建一个 Page
   }
}

objc_autoreleasePoolPop 方法

static inline void pop(void *token) {
    AutoreleasePoolPage *page = pageForPointer(token); // 获取当前token所在的page
    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();
        }
    }
}

autorelease 方法

在自动释放池内调用autorelease方法,最终会调用上面提到的 autoreleaseFast 方法,将当前对象加到 AutoreleasePoolPage 中。

 - [NSObject autorelease]
└── id objc_object::rootAutorelease()
    └── id objc_object::rootAutorelease2()
        └── static id AutoreleasePoolPage::autorelease(id obj)
            └── static id AutoreleasePoolPage::autoreleaseFast(id obj)

ARC下自动放入AutoreleasePool

  • alloc/new/copy/mutableCopy等持有对象的方法,不会加入autoreleasePool;其他不持有对象的方法通过objc_autoreleaseReturnValue和objc_retainAutoreleasedReturnValue来判断是否需要加入autoreleasePool,这是编译器的优化。
  • iOS5及之前的编译器,关键字__weak修饰的对象,会自动加入autoreleasePool;iOS5及之后的编译器,则直接调用的release,不会加入autoreleasePool。
  • id指针和对象指针(id *,NSError **),会自动加上关键字__autorealeasing,加入autoreleasePool。

Thread和AutoreleasePool的关系

  • 所有线程都维护有它自己的自动释放池的堆栈结构。新的自动释放池被创建的时候,它们会被添加到栈的顶部,而当池子销毁的时候,会从栈移除。
  • 对于当前线程来说,Autoreleased对象会被放到栈顶的自动释放池中。
  • 当一个线程停止,它会自动释放掉与其关联的所有自动释放池。

RunLoop和AutoreleasePool的关系

主线程

主线程的NSRunLoop在监测到事件响应开启每一次event loop之前,会自动创建一个autorelease pool,并且会在event loop结束的时候执行drain操作,释放其中的对象。

  • App启动后,苹果在主线程RunLoop里注册了两个Observer,第一个Observer监视的事件是Entry(即将进入Loop),其回调内会调用_objc_autoreleasePoolPush()创建自动释放池。
  • 第二个Observer监视了两个事件BeforeWaiting(准备进入休眠)时调用_objc_autoreleasePoolPop()和_objc_autoreleasePoolPush()释放旧的池并创建新池。
  • Exit(即将退出Loop)时调用 _objc_autoreleasePoolPop()来释放自动释放池。

子线程

如果没有开启RunLoop,是不会自动创建autoreleasePool;但是如果出现autorelease对象,则会自动创建autoreleasePool。

手动添加AutoreleasePool

AutoreleasePool需要手动添加的情况:循环中产生大量临时变量,占用大量内存;编写的不是基于UI框架的程序,例如命令行工具;使用非Cocoa程序创建的子线程。

AutoreleasePool什么时候释放

RunLoop结束/休眠的时候;调用pool的drain()方法
手动添加的pool, 对象 release 时机就是 } 结束

总结

  • 自动释放池是由 AutoreleasePoolPage 以双向链表的方式实现的
  • 当对象调用 autorelease 方法时,会将对象加入AutoreleasePoolPage 的栈中
  • 调用 AutoreleasePoolPage::pop 方法会向栈中的对象发送 release 消息
  • 使用autorelease的好处:不用关心对象释放时间;autorelease的本质:将对象释放时间延迟

参考链接

iOS底层学习 - 内存管理之Autoreleasepool
iOS内存管理-深入解析自动释放池

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值