【问题标题】:How to deal with ARC in a background thread?如何在后台线程中处理 ARC?
【发布时间】:2014-09-25 21:24:53
【问题描述】:

我了解自动引用计数的工作原理:

在编译时,确定对象之间可能的关系类型以及可能发生释放的位置,然后在运行时跟踪对每个对象的强指针引用的数量,并在该数量达到 0 时释放。至少在处理主线程时,我在概念上或实践中都没有遇到过这个问题。

我注意到当我启动一个新的后台线程时,它不会释放任何生成的对象,直到线程结束。我跑了一个例子:

本质上,在线程调用周围放置了一个自动的“@autoreleasepool”,所以很明显,放置我自己的不会解决这个问题。事实上,我已经用相同的结果进行了测试。如果我在这方面是正确的,那么该强制池的存在正是导致我的问题的原因,但我认为这是 ARC 可以在多线程应用程序上执行的最佳操作。内存使用缓慢且持续地倾斜。如果我离开这个线程太久,应用程序最终会耗尽内存。这是一个问题,因为线程需要能够在最坏的情况下无限期地运行。

我已经删除了线程中的一些主要分配。我相信我已经确定一些剩余的内存分配是从 NSMutableArray 释放的 NSNumber,因为我正在覆盖它。

所以我想我必须执行以下操作之一:

  1. 完全删除线程中的一致性分配。
  2. 将应用更改为非 ARC 以在后台线程中手动释放内存。
  3. 检测内存高时,保存线程状态,与主线程同步释放对象,然后恢复算法。
  4. 了解是否有某种方法可以通知主线程或 ARC 我想要同步对象以便释放它。
  5. 意识到 Apple 实际上有一种方法可以调度 ARC 以正确地异步处理另一个线程,并且从未在主要参考页面上提及它。
  6. 在具有分配对象的文件(例如字典)中禁用 ARC。 How can I disable ARC for a single file in a project?

这些看起来都不是我的问题的完美解决方案,尽管我可能会尝试 1 或 6。有人有什么建议吗?

更新:

我运行了相同的算法,但在代码中添加了以下自动释放块,起初我以为我要反驳 rmaddy 和 Aaron Brager 的回答。

-(void)setInt: (int)value For: (NSString *)variableName {
    @autoreleasepool {
        [self.intDictionary setValue:@(value) forKey:variableName];
    }
}

-(void)setBool: (bool)value For: (NSString *)variableName {
    @autoreleasepool {
        [self.boolDictionary setValue:@(value) forKey:variableName];
    }
}

这是生成的内存分配图:

他们是正确的。我很高兴在这种情况下我错了。这意味着我的编码将比我开始想象的要容易得多。

【问题讨论】:

  • 我建议在仪器中使用僵尸分析器来确定未发布的内容。此外,如果您提供一些与您的后台线程实现相关的代码,这将对(我)有所帮助。
  • 这一切都与线程无关。你只是碰巧在你的后台线程上创建了很多自动释放的对象。只需在适用的情况下添加 @auoreleasepool 的其他用途。
  • @rmaddy ...这似乎也与ARC关系不大。
  • @rmaddy 抱歉,这行不通。整个线程调度已经在一个池中。添加嵌套池不会覆盖它。即使可以,我也需要从字典中动态释放额外的对象,我不会在同一个块中创建和释放它们,而不首先离开该块不确定的次数。
  • @rmaddy 啊,你实际上是对的,maddy。显然,内部调用是受青睐的,即使之前分配了一些东西,它也可能在块的末尾被释放。参考图片更新。

标签: ios objective-c multithreading memory-management automatic-ref-counting


【解决方案1】:

自动释放对象在其自动释放池耗尽时被释放。这通常发生在线程的运行循环结束时。

无论您是在谈论主线程还是后台线程,此行为都是相同的。当然,它只适用于不再有任何强引用的对象。

你的这个分析并不完全正确:

本质上,在线程调用周围放置了一个自动“@autoreleasepool”,所以很明显,放置我自己的不会解决这个问题。

考虑这段代码:

@autoreleasepool {
    for (int i = 0; i < 100000; i++) {
        // create an expensive autoreleased object
        // do something with it
    }
}

在这种情况下,不会释放任何自动释放的对象,直到运行循环结束时自动释放池耗尽。

但是,如果您添加自己的自动释放池:

@autoreleasepool {
    for (int i = 0; i < 100000; i++) {
        @autoreleasepool {
            // create an expensive autoreleased object
            // do something with it
        }
    }
}

对于for 循环的每次迭代,当内部池耗尽时,对象将被释放。

如果如上所示添加您自己的自动释放池并不能解决您的问题,那么剩下的可能性是:

  1. 您正在使用 ARC,并且对象没有被释放,因为它们仍然有一个强引用(也许您有一个强引用循环)
  2. 您增加的内存使用量并非来自 Objective-C 对象(例如,您创建了一个 CGImageRef 并且从未调用过 CGImageRelease)
  3. 您错误地没有将 ARC 用于此类(并且您没有调用 release)

您提出的六个解决方案也可以解决问题,但可能比这更容易解决。

【讨论】:

  • '@autoreleasepool' 是唯一释放内存的地方吗?我很好奇在额外线程结束后我的内存是如何下降的,因为我的应用程序中唯一的“@autoreleasepool”是包装 appdelegate 的那个。 TableViewControllers 有自动生成的吗?
  • 您的应用程序委托中的自动释放池用于您的主线程(通常是表视图控制器运行的地方)。如果您使用的是 Grand Central Dispatch (dispatch_async…),则会为您创建自动释放池,但 you're encouraged to add your own。
  • 啊,我明白了。但是,我现在对可能只是一个小问题感到困惑。您是否注意到我的自动释放池块仅在我释放内存的地方而不是我分配它的地方?那么,为什么它甚至是一个块?如果它只发生在块的末尾,为什么它不只是一些函数调用呢?这是为了防止某人在块内有一个 return 语句或其他什么,只是为了强制达到实际发布?
  • 块需要一个开始,因为这是创建自动释放池的时候。 (自动释放的对象被添加到这个池中)。
猜你喜欢
  • 2023-02-03
  • 1970-01-01
  • 1970-01-01
  • 2016-01-27
  • 1970-01-01
  • 1970-01-01
  • 2012-08-26
  • 2018-04-20
  • 2011-02-09
相关资源
最近更新 更多