提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
在IOS程序中main.m文件,是整个IOS应用的入口,@autoreleasepool{}这个block包裹了整个应用,对象创建时自动执行autorelease将对象加入到autoreleasepool,进而管理整个IOS应用的内存回收,不用在创建对象的最后再手动release。那么下面从源码的角度来分析autoreleasepool究竟是怎样工作的。
一、@autoreleasepool
@autoreleasepool到底是什么?在命令行中使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp 让编译器重新改写以下代码:
于是在目录下生成main.cpp文件,删除无用代码,具体如下:
由上图知道只是声明了__AtAutoreleasePool这样的一个结构,在main.cpp中查找这个结构,代码如下:
由上图可知,__AtAutoreleasePool创建的时候调用了objc_autoreleasePoolPush并返回第一页的POOL_BOUNDARY(哨兵对象),析构的时候调用objc_autoreleasePoolPop并传入POOL_SENTINEL,所以其实main函数的实际工作应该是这样的:
二、AutoreleasePool
由上节可知@autoreleasepool只是调用了objc_autoreleasePoolPush和objc_autoreleasePoolPop,这两个函数都在NSObject.mm文件中,代码如下:
所以下面分为4个小节,来详细说明AutoreleasePool如何实现:
-. AutoreleasePoolPage的结构
-. objc_autoreleasePoolPush方法
-. POOL_BOUNDARY(哨兵对象)
-. objc_autoreleasePoolPop
-. autorelease
2.1 AutoreleasePoolPage结构
AutoreleasePool实际就是AutoreleasePoolPage组成的双向链表,AutoreleasePoolPage是一个是一个C++类(NSObject.mm),代码如下:
AutoreleasePoolPage继承于AutoreleasePoolPageData结构体(NSObject-internal.h),所有有关的Page数据都放在了这个结构体中:
假设一个AutoreleasePoolPage 被初始化在内存的 0x100816000 ~ 0x100817000 中,它在内存中的结构如下:
2.2 POOL_BOUNDARY(哨兵对象)
POOL_BOUNDARY只是 nil 的别名。每个Page都有一个POOL_BOUNDARY,相当于Page有关信息和obj对象的分界线。
在执行objc_autoreleasePoolPush方法的时候会把POOL_BOUNDARY加入到第一页,而且返回这个哨兵对象。
在objc_autoreleasePoolPop调用时,传入的就是这个POOL_BOUNDARY,向自动释放池中的对象发送 release 消息,直到第一个 POOL_SENTINEL。
2.3 objc_autoreleasePoolPush方法
objc_autoreleasePoolPush作用是创建自动释放池的第一页,并返回第一页的POOL_BOUNDARY(哨兵对象)。代码如下:
由上图可知,它调用了AutoreleasePoolPage 的类方法 push方法,代码如下:
在这里会进入一个比较关键的方法 autoreleaseFast,并传入哨兵对象 POOL_SENTINEL。
- 情况一(页面存在且不满):
调用add方法将对象添加到自动释放池,核心逻辑就是将obj添加到next位置,next指针后移,代码如下:
- 情况二(页面存在且装满):
调用autoreleaseFullPage,创建新page,链接到琏表最后,并设置成hotPage,再进行add,这里返回的就是POOL_BOUNDARY,代码如下:
- 情况三(页面不存在):
调用autoreleaseNoPage(NSObject.mm),创建新page,并设置成hotPage再进行add,这里返回的就是POOL_BOUNDARY,这里删除了一些debug代码,具体代码如下:
2.4 objc_autoreleasePoolPop 方法
objc_autoreleasePoolPop作用对自动释放池中的对象发送release消息,一直到传入的*ctxt指针,但是在整个工程并没有发现传入其他对象的例子。不过在这个方法中传入其它的指针也是可行的,会将自动释放池释放到相应的位置。代码如下:
由上图可知,它调用了AutoreleasePoolPage 的类方法 POP(NSObject.mm)方法,删除了一些debug代码,具体代码如下:
再看popPage(NSObject.mm)方法,删除了一些debug代码,具体代码如下:
真正释放obj到stop的方法是releaseUntil(NSObject.mm),删除了一些debug代码,具体实现如下:
对page页面进行内存释放的是kill方法,目的删除当前page后的所有page,具体代码如下:
2.5 autorelease 方法
- autorelease的作用就是将当前对象加入到所在的自动释放池。从NSObject的autorelease(objc-object.h)开始执行:
- 执行rootAutorelease方法(objc-object.h):
- 执行rootAutorelease2方法(NSObject.mm):
- 执行AutoreleasePoolPage的autorelease(NSObject.mm)方法:
- 执行AutoreleasePoolPage的 autoreleaseFast (NSObject.mm)方法,将obj加入到自动释放池,autoreleaseFast参考2.3节。