iOS深入理解Autoreleasepool(自动释放池)

一、 @autoreleasepool{}

OC代码 复制代码
int main(int argc, const char * argv[]) {
    @autoreleasepool {
    
    }
    return 0;
}

我们平时创建一个main函数的代码的时候,就会发现其中有一个这个东西@autoreleasepool{},使用clang编译之后:@autoreleasepool{...}被编译成了{__AtAutoreleasePool __autoreleasepool; ... }

这个__AtAutoreleasePool到底是什么?

它其实是一个结构体,在创建__AtAutoreleasePool结构体变量的时候调用了objc_autoreleasePoolPush(void),销毁的时候会调动objc_autoreleasePoolPop(void *),其实构造函数和析构函数,所以我们可以看出其其实是一个C++封装的自动释放池变量,会将@autoreleasepool{...}中{}中的内容添加到自动释放池中,方便内存管理。

C++代码 复制代码
struct __AtAutoreleasePool {
    // 构造函数
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
    // 析构函数
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};

二、AutoreleasePoolPage

从上边的__AtAutoreleasePool我们可以看到这两个方法objc_autoreleasePoolPushobjc_autoreleasePoolPop

javascript 复制代码
void *objc_autoreleasePoolPush(void) {
    return AutoreleasePoolPage::push();
}
 
void objc_autoreleasePoolPop(void *ctxt) {
    AutoreleasePoolPage::pop(ctxt);
}

我们可以看出这里又引入了新的类AutoreleasePoolPage

arduino 复制代码
class AutoreleasePoolPage {
    magic_t const magic;//AutoreleasePoolPage 完整性校验
    id *next;//存放下一个autorelease对象的地址
    pthread_t const thread; //AutoreleasePoolPage 所在的线程
    AutoreleasePoolPage * const parent;//父节点
    AutoreleasePoolPage *child;//子节点
    uint32_t const depth;//深度,也可以理解为当前page在链表中的位置
    uint32_t hiwat;
}

每一个自动释放池都是由一系列AutoreleasePoolPage组成的,并且每一个AutoreleasePoolPage的大小都是4096字节(16 进制 0x1000)。

所以我们从上述的源码可以看出,自动释放池其实就是一个由AutoreleasePoolPage构成的双向链表 ,其结构中的childparent分别指向其前趋和后继。

其中有 56 bit 用于存储AutoreleasePoolPage的成员变量,剩下的0x100816038 ~ 0x100817000都是用来存储加入到自动释放池中的对象。

  • 调用push方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址

  • 调用pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY

  • id *next指向了下一个能存放autorelease对象地址的区域

三、Runloop和Autorelease

AutoreleasePool创建

  • App启动后,苹果在主线程RunLoop里注册了两个Observer,其回调都是_wrapRunLoopWithAutoreleasePoolHandler()
  • 第一个Observer监视的事件是Entry(即将进入Loop),其回调内会调用
    _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。

AutoreleasePool释放

  • 第二个Observer监视了两个事件: BeforeWaiting(准备进入休眠)
    时调用_objc_autoreleasePoolPop()_objc_autoreleasePoolPush()释放旧的池并创建新池;Exit(即将退出Loop)时调用 _objc_autoreleasePoolPop()来释放自动释放池。这个Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
  • 在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的AutoreleasePool环绕着,所以不会出现内存泄漏。

四、面试题AutoreleasePool的原理

  • 自动释放池的本质是一个AutoreleasePoolPage结构体对象,是一个栈结构存储的页,每一个AutoreleasePoolPage都是以双向链表的形式连接

  • 自动释放池的压栈出栈主要是通过结构体的构造函数和析构函数调用底层的objc_autoreleasePoolPushobjc_autoreleasePoolPop,实际上是调用AutoreleasePoolPagepushpop两个方法。

  • 调用push操作其实就是创建一个新的AutoreleasePoolPage,而AutoreleasePoolPage的具体操作就是插入一个哨兵POOL_BOUNDARY,并返回插入哨兵POOL_BOUNDARY的内存地址。而push内部调用autoreleaseFast方法处理,主要有以下三种情况:

    • a.当page存在,且不满时,调用add方法将对象添加至pagenext指针处,并将next指向下一位;
    • b.当page存在,且已满时,调用autoreleaseFullPage初始化一个新的page,然后调用add方法将对象添加至page栈中;
    • c.当page不存在时,调用autoreleaseNoPage创建一个hotPage,然后调用add方法将对象添加至page栈中。
  • 调用pop操作时,会传入一个值,这个值就是push操作的返回值,即哨兵POOL_BOUNDARY内存地址token。所以pop内部的实现就是根据token找到哨兵对象所处的page中,然后使用 objc_release 释放 token之前的对象,并把next 指针到正确位置。

相关推荐
游戏开发爱好者83 分钟前
如何在 Windows 环境下测试 iOS App,实时日志,CPU监控
android·ios·小程序·https·uni-app·iphone·webview
Star Learning Python31 分钟前
SQL server-2025年面试题目和答案
面试·职场和发展
a努力。1 小时前
得物Java面试被问:B+树的分裂合并和范围查询优化
java·开发语言·后端·b树·算法·面试·职场和发展
a程序小傲1 小时前
中国电网Java面试被问:Kafka Consumer的Rebalance机制和分区分配策略
java·服务器·开发语言·面试·职场和发展·kafka·github
ii_best1 小时前
免越狱!按键精灵鹰眼群控让电脑批量掌控 iOS 设备,功能介绍
ios·自动化·电脑
NAGNIP10 小时前
一文搞懂树模型与集成模型
算法·面试
NAGNIP10 小时前
万字长文!一文搞懂监督学习中的分类模型!
算法·面试
技术狂人16810 小时前
工业大模型工程化部署实战!4 卡 L40S 高可用集群(动态资源调度 + 监控告警 + 国产化适配)
人工智能·算法·面试·职场和发展·vllm
007php00712 小时前
mySQL里有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据
数据库·redis·git·mysql·面试·职场和发展·php
鱼跃鹰飞13 小时前
JMM 三大特性(原子性 / 可见性 / 有序性)面试精简版
java·jvm·面试