建议先在网上搜索@autoreleasepool的文章,看看底层的结构,网上大部分文章都有清楚的描述
在ARC下,已经不允许使用NSAutoreleasePool对象了,并且根据官方文档,@autoreleasepool比它更高效,因此这里只讨论@autoreleasepool。
@autoreleasepool最重要的两个入口函数如下:
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
push操作往poolpage中插入一个标记,而pop则根据这个标记来给这一区间的所有对象发送release消息。
那么在哪里调用pop呢?
有一种说法是:在每次运行循环结束的时候执行释放操作。
这一种说法的原因是,你在主线程打印当前runloop,可以明显看到注册了几种observer
activities = 0x1,对应的就是kCFRunLoopEntry
;
activities = 0xa0,对应的就是kCFRunLoopBeforeWaiting | kCFRunLoopExit
它们的回调函数为_wrapRunLoopWithAutoreleasePoolHandler ()
,这个函数在entry
的时候调用push()
,在beforeWaitting
的时候调用pop()
和push()
,在exit
时调用pop
这么一看,很有道理。
但我觉得这个回答不够全面。
根据官方文档,autoreleasepool block结束的地方(也就是结束的}
)会调用pop()
方法,并且举的例子,就是在for循环里面使用,如果是线程休眠的时候,那么在主线程中这种用法还有什么意义呢?因为大量for循环导致程序根本无法运行到runloop休眠的时候内存就已经暴增了。
因此,我的理解是,autoreleasepool block结束的时候,会调用pop()
方法,给池中对象发送release
消息。
那么为什么主线程会多做操作?这是因为在main函数中:
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
此时是无法运行到autoreleasepool block结束的地方,因此主线程会做这样的操作。
那么,子线程会类似主线程,监听runloop,释放自动释放池吗?
我们做以下实验:
- (void)viewDidLoad {
[super viewDidLoad];
_thread = [[NSThread alloc] initWithTarget:self selector:@selector(createThread) object:nil];
[_thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self performSelector:@selector(dosomething) onThread:_thread withObject:nil waitUntilDone:NO];
}
- (void)createThread {
@autoreleasepool {
TestButton *btn1 = [TestButton buttonWithType:UIButtonTypeCustom];
NSLog(@"inner = %p",btn1);
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
}
}
- (void)dosomething {
TestButton *btn2 = [TestButton buttonWithType:UIButtonTypeCustom];
NSLog(@"outter = %p",btn2);
}
在上面代码中,btn1和btn2都是自动释放的对象,我们期望btn1和btn2都能被释放。
运行结果为btn2被释放,btn1不被释放。
注意这里btn2释放,从汇编代码和逻辑推断是在函数结束的时候就释放了(局部变量出了作用域),至于后面有没有被系统在runloop休眠的时候释放(走pop()
流程,但因为已经被释放,相当于什么都不做)无从得知。
btn1没有被释放,如果按照上面的说法:在每次运行循环结束的时候执行释放操作,那么可以反证出,子线程的runloop并没有被监听用于pop()
。
因此,我得出的结论是:1. @autoreleaspool释放时机是在block结束的时候。2. 不过主线程监听了runloop的状态,在不同状态分别提前释放和重建了自动释放池。3. 子线程不会像主线程这么做。
最后,有个疑问:
If your application or thread is long-lived and potentially generates a lot of autoreleased objects, you should use autorelease pool blocks (like AppKit and UIKit do on the main thread); otherwise, autoreleased objects accumulate and your memory footprint grows.
根据文档,如果创建一个常驻线程,并且由大量自动释放对象,则应该像UIKit在主线程那样添加@autoreleasepool,否则内存将会增大。
这和我上面写的例子是一样的,但是根据上面推论,子线程是不会自动像主线程那样监听runloop来释放池和重建池的,那么添加@autoreleasepool有什么用呢(AFN中也添加了@autoreleasepool)?
这个点一直想不通,如果你知道,欢迎在评论区提出。或者你对文章其他部分有异议,也欢迎指出。
网友评论