【问题标题】:Memory leak, despite no strong references?内存泄漏,尽管没有强引用?
【发布时间】:2021-07-20 07:22:56
【问题描述】:

我正在做一个性能测试,试图在我的 Mac 应用程序中测量一个重要的NSOutlineView 的渲染性能。在此过程中,我循环了几次,创建视图,将其嵌入到虚拟窗口中,并将其渲染为图像。我概括了一下,但大致是这样的:

// Intentionally de-indented these for easier reading in this narrow page
class MyPerformanceTest: XCTestCase { reading
func test() { 
measure() {
// autoreleasepool {

    let window: NSWindow = {
        let w = NSWindow(
            contentRect: NSRect.init(x: 100, y: 100, width: 800, height: 1200),
            styleMask: [.titled, .resizable, .closable, .miniaturizable],
            backing: .buffered,
            defer: false
        )
        w.tabbingMode = .disallowed
        w.cascadeTopLeft(from: NSPoint(x: 200, y: 200))
        w.makeKeyAndOrderFront(nil)
        w.contentView = testContentView // The thing I'm performance testing
        return w
    }()

    let bitmap = self.bitmapImageRepForCachingDisplay(in: self.frame)
        .map { bitmap in
            self.cacheDisplay(in: self.frame, to: bitmap)
            return bitmap
        }

    let data = bitmap.representation(using: .png, properties: [:])!

    saveToDesktop(data, name: "image1.png") // Helper function around Data.write(to:). Boring.

    window.isReleasedWhenClosed = false // Defaults to true, but crashes if true.
    window.close()
    
// }
}
}
}

我注意到这会增加内存使用量。在我的measure(_:) 块的每个循环中分配的每个窗口都存在。这是有道理的,因为我没有运行主运行循环,因此线程的自动释放池永远不会耗尽。我将整个measure 块封装在对autoreleasepool block 的调用中,这已解决。使用内存图调试器,我确认只有 1 个窗口最大值,这将是当前迭代中的窗口。太好了。

但是,我发现我的 NSOutlineView、它们的行和它们的行模型仍然存在。它们有数千个,所以它真的炸毁了内存使用量。

我使用 Instruments 中的 Leaks 工具对其进行了分析:没有泄漏。

然后我检查了内存图调试器中的对象。没有明显的强引用循环,所有对象都有与本例类似的情况。它是一个 NSOutlineView(嗯,一个动态的NSKVONotifying_* 子类,但这没关系),只有一个来自 ObjC 块的强引用。但是该块仅被一个引用(黑线)弱引用。这整件事不应该被释放吗?

我该如何解决为什么它会保持活动状态?

【问题讨论】:

  • 您是否在setup 中设置了任何内容?重新发现这些东西一直存在直到所有测试都结束了,这总是让我感到惊讶。
  • 不,根本没有setup。顺便说一句,这个测量是在测量块进行的,就像第 10 次迭代(共 100 次)一样。
  • window.isReleasedWhenClosed = false 怎么样?这不会让窗户堆积起来吗?
  • 无论如何,真正的正确方法可能是跟踪保留/释放和使用仪器。
  • 太好了,谢谢!我现在正在上班,等我开始我的个人工作时再看看:D

标签: swift automatic-ref-counting instruments appkit nsoutlineview


【解决方案1】:

我该如何解决为什么它会保持活动状态?

使用乐器。

为分配模板配置工具。在开始记录之前,在文件 > 记录选项下,将分配模板选项配置为记录引用计数。

录制并暂停。选择要研究的轨道区域。找到您要研究的对象类型,然后点击小右箭头以显示该类型的所有对象。在列表中选择一个。点击地址旁边的小右箭头。

您现在将看到保留和释放的历史记录以及正在运行的引用计数。选择保留/释放会在右侧显示调用堆栈。从而可以推断出这个对象的内存管理历史。

【讨论】:

  • 天哪,这太不可思议了。我认为分配工具只能跟踪第一个 malloc 和我不知道的最终 free 。我知道那个“录制选项”面板,但我总是忘记窥探那里以了解此类新功能。
  • 我会暂时保留这个问题的新答案,但我稍后会接受。现在用这个新的力量四处寻找,甜!
  • 您认为这些自动保留/释放配对有多可靠?我应该持怀疑态度并全部核实,还是只将它们作为基本事实?
  • 所有你想知道的是,谁留住了你没想到的。编译器不会在 ARC 配对中出错。
  • 顺便说一下,如果你在Scheme编辑器中打开它,内存图也会向你显示malloc堆栈。但它不会显示完整的保留和释放列表;只有 Instruments 可以做到这一点。
猜你喜欢
  • 2017-07-17
  • 1970-01-01
  • 2011-06-23
  • 2012-01-25
  • 2013-04-30
  • 1970-01-01
  • 1970-01-01
  • 2017-01-13
  • 1970-01-01
相关资源
最近更新 更多