美文网首页
关于@autoreleasepool的释放时机,个人看法

关于@autoreleasepool的释放时机,个人看法

作者: 9a957efaf40a | 来源:发表于2019-02-25 11:20 被阅读1次

建议先在网上搜索@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

该说法内容来自:RunLoop总结:RunLoop 与GCD 、Autorelease Pool之间的关系

这么一看,很有道理。

但我觉得这个回答不够全面。

根据官方文档,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)?

这个点一直想不通,如果你知道,欢迎在评论区提出。或者你对文章其他部分有异议,也欢迎指出。

相关文章

网友评论

      本文标题:关于@autoreleasepool的释放时机,个人看法

      本文链接:https://www.haomeiwen.com/subject/imdwyqtx.html