阅读 280

小码哥iOS学习笔记第二十六天: @autoreleasepool

一、@autoreleasepool编译后的代码

  • 定义Person类继承自NSObject, 在main函数中代码如下

  • 通过终端cdmain.m所在文件夹, 并执行下面的命令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
复制代码
  • 将生成的main.cpp文件拖到项目中并打开, 可以找到main.m文件在底层的代码

  • 整理后, 就是下面的代码

  • 搜索__AtAutoreleasePool结构体

  • 整理后如下

  • 我们知道, 一个自动释放池会自动销毁, 所以main函数中的代码可以模拟为下图所示

  • 所以, 想要了解@autoreleasepool, 可以从objc_autoreleasePoolPush函数和objc_autoreleasePoolPop函数入手

二、objc_autoreleasePoolPush 和 objc_autoreleasePoolPop

  • objc_autoreleasePoolPush函数的底层实现

  • objc_autoreleasePoolPop函数的底层实现

  • 可以看到objc_autoreleasePoolPushobjc_autoreleasePoolPop, 都是用了AutoreleasePoolPage
  • 下图中展示的是AutoreleasePoolPage中的主要成员变量

自动释放池的主要底层数据结构是:__AtAutoreleasePool、AutoreleasePoolPage
调用了autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的

三、AutoreleasePoolPage的结构

  • 每个AutoreleasePoolPage对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址

  • AutoreleasePoolPage中除了一开始的56个字节用来存储成员变量, 其他的所有内存空间都是用来存储被autorelease对象的地址
  • 其中beginend两个函数的实现如下

begin = AutoreleasePoolPage地址 + AutoreleasePoolPage的大小
end = AutoreleasePoolPage地址 + SIZE(4096)

  • 当一个AutoreleasePoolPage不够存储autorelease对象地址时, 就会在创建一个AutoreleasePoolPage
  • 所有的AutoreleasePoolPage对象通过双向链表的形式连接在一起

四、AutoreleasePoolPage的push和pop

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

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

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

  • 可以通过以下私有函数来查看自动释放池的情况 extern void _objc_autoreleasePoolPrint(void);

  • 使用_objc_autoreleasePoolPrint函数, 查看一个空的autoreleasepool

  • 可以看到, 此时自动缓冲池中并没有任何的autorelease对象

  • 添加一个autorelease对象

  • 可以看到page中有两个缓存, 其中一个POOL就是POOL_BOUNDARY, 第二个就是加入的Person对象

  • 再添加一个autorelease对象

  • 此时page中存储三个内容, 一个POOL_BOUNDARY, 两个Person对象

  • 嵌套一层@autoreleasepool, 代码如下

  • 此时可以看到page中存储了两个POOL_BOUNDARY

  • 再次嵌套一层@autoreleasepool, 代码如下

  • 此时可以看到page中存储了三个POOL_BOUNDARY

  • @autoreleasepool存放1000Personautorelease对象

  • 可以看到, 一个page存储空间用满了, 会再次创建一个page

五、Runloop和Autorelease

  • iOS在主线程的Runloop中注册了2个Observer
  • 第1个Observer监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()
  • 第2个Observer
  • 监听了kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
  • 监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()