Autoreleasepool
Autoreleasepool看过好几遍了,每次看完就会忘记,感觉还是要记录下来。
@autoreleasepool
编写一个main.m代码文件,类似于:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Hello, World!");
}
return 0;
}
在终端中使用clang -rewrite-objc main.m
命令将上述OC代码重写成C++的实现。生成的代码主要是下面2处:
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;
};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_8d_3vn1mhhd5sx58zhlpyzw1wgh0000gn_T_main_171318_mi_0);
}
return 0;
}
看到这里,苹果还是很巧妙的:
先调用创建__autoreleasepool对象,自动调用构造方法去Push;然后运行完成,会自动调用析构方法Pop。
POOL_BOUNDARY (苹果这个设计真的很巧妙)
POOL_BOUNDARY
的前世叫做POOL_SENTINEL
,称为哨兵对象或者边界对象;
POOL_BOUNDARY
用来区分不同的自动释放池,以解决自动释放池嵌套的问题;
每当创建一个自动释放池,就会调用push()方法将一个POOL_BOUNDARY
入栈,并返回其存放的内存地址;
当往自动释放池中添加autorelease对象时,将autorelease对象的内存地址入栈,它们前面至少有一个POOL_BOUNDARY
;
当销毁一个自动释放池时,会调用pop()方法并传入一个POOL_BOUNDARY
,会从自动释放池中最后一个对象开始,依次给它们发送release消息,直到遇到这个POOL_BOUNDARY
。
AutoreleasePoolPage
双向链表,每个Page的内存大小为4096个字节,每当Page满了的时候,就会创建一个新的Page。hotPage()方法就是用来获得这个新创建的未满的Page。
autoreleaseFast()在执行过程中有三种情况:
- 当前Page存在且未满时,通过page->add(obj)将autorelease对象入栈,即添加到当前Page中;
- 当前Page存在但已满时,通过autoreleaseFullPage(obj, page)创建一个新的Page,并将autorelease对象添加进去;
- 当前Page不存在,即还没创建过Page,通过autoreleaseNoPage(obj)创建第一个Page,并将autorelease对象添加进去。
释放时机
网上对于iOS工程的main()函数中的@autoreleasepool有一种解释:
在iOS工程的main()函数中有一个@autoreleasepool,这个@autoreleasepool负责了应用程序所有autorelease对象的释放。
其实这个解释是错误的。
如果你的程序使用了AppKit或UIKit框架,那么主线程的RunLoop就会在每次事件循环迭代中创建并处理@autoreleasepool。也就是说,应用程序所有autorelease对象的都是由RunLoop创建的@autoreleasepool来管理。而main()函数中的@autoreleasepool只是负责管理它的作用域中的autorelease对象。
那么子线程中的怎么办呢?
在子线程你创建了 Pool 的话,产生的 Autorelease 对象就会交给 pool 去管理。如果你没有创建 Pool ,但是产生了 Autorelease 对象,就会调用 autoreleaseNoPage 方法。在这个方法中,会自动帮你创建一个 hotpage(hotPage 可以理解为当前正在使用的 AutoreleasePoolPage,如果你还是不理解,可以先看看 Autoreleasepool 的源代码,再来看这个问题 ),并调用 page->add(obj)将对象添加到 AutoreleasePoolPage 的栈中,也就是说你不进行手动的内存管理,也不会内存泄漏啦。