草庐IT

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

9a957efaf40a 2023-09-21 原文

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

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

补一张打印主线程runloop各种observer的图:


image.png

有关关于@autoreleasepool的释放时机,个人看法的更多相关文章

  1. ruby-on-rails - 关于 Ruby 的一般问题 - 2

    我在我的rails应用程序中安装了来自github.com的acts_as_versioned插件,但有一段代码我不完全理解,我希望有人能帮我解决这个问题class_eval我知道block内的方法(或任何它是什么)被定义为类内的实例方法,但我在插件的任何地方都找不到定义为常量的CLASS_METHODS,而且我也不确定是什么here,并且有问题的代码从lib/acts_as_versioned.rb的第199行开始。如果有人愿意告诉我这里的内幕,我将不胜感激。谢谢-C 最佳答案 这是一个异端。http://en.wikipedia

  2. ruby - 我怎样才能更好地了解/了解更多关于 Ruby 的知识? - 2

    按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭9年前。我最近开始学习Ruby,这是我的第一门编程语言。我对语法感到满意,并且我已经完成了许多只教授相同基础知识的教程。我已经写了一些小程序(包括我自己的数组排序方法,在有人告诉我谷歌“冒泡排序”之前我认为它非常聪明),但我觉得我需要尝试更大更难的东西来理解更多关于Ruby.关于如何执行此操作的任何想法?

  3. ruby - 关于 Ruby 中 Dir[] 和 File.join() 的混淆 - 2

    我在Ruby中遇到了一个关于Dir[]和File.join()的简单程序,blobs_dir='/path/to/dir'Dir[File.join(blobs_dir,"**","*")].eachdo|file|FileUtils.rm_rf(file)ifFile.symlink?(file)我有两个困惑:首先,File.join(@blobs_dir,"**","*")中的第二个和第三个参数是什么意思?其次,Dir[]在Ruby中有什么用?我只知道它等价于Dir.glob(),但是,我对Dir.glob()确实不是很清楚。 最佳答案

  4. elasticsearch源码关于TransportSearchAction【阶段三】 - 2

    1.回顾.TransportServicepublicclassTransportServiceextendsAbstractLifecycleComponentTransportService:方法:1publicfinalTextendsTransportResponse>voidsendRequest(finalTransport.Connectionconnection,finalStringaction,finalTransportRequestrequest,finalTransportRequestOptionsoptions,TransportResponseHandlerT>

  5. 关于Qt程序打包后运行库依赖的常见问题分析及解决方法 - 2

    目录一.大致如下常见问题:(1)找不到程序所依赖的Qt库version`Qt_5'notfound(requiredby(2)CouldnotLoadtheQtplatformplugin"xcb"in""eventhoughitwasfound(3)打包到在不同的linux系统下,或者打包到高版本的相同系统下,运行程序时,直接提示段错误即segmentationfault,或者Illegalinstruction(coredumped)非法指令(4)ldd应用程序或者库,查看运行所依赖的库时,直接报段错误二.问题逐个分析,得出解决方法:(1)找不到程序所依赖的Qt库version`Qt_5'

  6. ruby - 如何强制 Ruby 释放内存给操作系统 - 2

    正如标题,我有一个处理大量数据的ruby​​程序。该程序占用了所有内存,其中调用了系统命令hostname,并且发生错误无法分配内存-主机名我试过GC.start但它不起作用。那么如何强制ruby释放未使用的内存呢?OK,这是别人的测试代码,最后报错是big_var被回收了。但是内存仍然没有释放。require"weakref"defreportputs"#{param}:\t\tMemory"+`psax-opid,rss|grep-E"^[[:space:]]*#{$$}"`.strip.split.map(&:to_i)[1].to_s+'KB'endbig_var=""#big

  7. ruby - 关于 Ruby/ChefSpec 编码风格的反馈 - 2

    我是Ruby的新手,但过去两周我一直在对Chef测试进行大量研究。该测试使用ChefSpec和Fauxhai,但它看起来不是很“像ruby”,我希望社区能给我一些编码风格的建议。有没有更好的方法来编写这样的嵌套循环?Recipe/foo/recipes/default.rbpackage"foo"doaction:installendRecipe/foo/spec/default_spec.rbrequire'chefspec'describe'foo::default'doplatforms={"debian"=>['6.0.5'],"ubuntu"=>['12.04','10.04

  8. ruby - 关于 ruby​​ 类变量的困惑 - 2

    假设一个使用类变量的简单ruby​​程序,classHolder@@var=99defHolder.var=(val)@@var=valenddefvar@@varendend@@var="toplevelvariable"a=Holder.newputsa.var我猜结果应该是99,但输出不是99。我想知道为什么。由于类变量的范围是类,我假设@@var="toplevelvariable"行不会影响类中的变量。 最佳答案 @@var是Holder的类变量。而顶层的@@var不是Holder的同名类变量@@var,是你在创建类Obj

  9. 一文解决关于VLAN所有的疑惑 - 2

    一文解决关于VLAN所有的疑惑VLAN基本概念为什么需要VLAN?怎么在交换机上划分VLAN,VLAN的工作原理有了子网,已经隔离了广播,还需要VLAN干啥?只进行子网划分,不进行VLAN划分VLAN划分与子网划分附加VLAN信息的方法VLAN划分交换机的端口类型(Access和Trunk)一、访问链接二、汇聚链接汇聚链接VLAN间通信为什么要进行VLAN间通信?路由器实现VLAN间通信路由器和交换机的连接方式通信细节三层交换机实现VLAN间通信加速VLAN间通信三层交换机与路由器三层交换机路由器路由器和交换机配合构建LAN的实例使用VLAN设计局域网的特点VLAN增加网络的灵活性不使用VLA

  10. ruby - 关于 CoffeeScript 变量范围的困惑 - 2

    我正在尝试了解CoffeeScript变量的范围。根据文档:ThisbehavioriseffectivelyidenticaltoRuby'sscopeforlocalvariables.但是,我发现它的工作方式不同。在CoffeeScript中a=1changeValue=->a=3changeValue()console.log"a:#{a}"#Thisdisplays3在ruby中a=1deffa=3endputsa#Thisdisplays1有人能解释一下吗? 最佳答案 Ruby的局部变量(以[a-z_]开头)arerea

随机推荐