美文网首页
关于@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