没有强引用,为什么还会出现内存泄漏?

3
我正在进行一项性能测试,试图测量我的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(_:)块的每个循环中分配的每个窗口都一直存在,这是有道理的,因为我没有运行主run loop,所以线程的自动释放池从未被清除。我在整个measure块中包装了一个autoreleasepoolblock的调用,问题得到解决。使用内存图调试器,我确认最多只有1个窗口,即当前迭代的窗口。太好了。
但是,我发现我的NSOutlineViews、它们的行和行模型仍然存在。有数千个,所以它真的让内存使用量飙升。
我使用Instruments的Leak工具进行了性能分析:没有泄漏。
然后我在内存图调试器中检查了这些对象。没有明显的强引用循环,所有对象的情况都类似于这个例子。它是一个NSOutlineView(实际上是一个动态的NSKVONotifying_*子类,但这并不重要),只有一个来自ObjC块的强引用。但是那个块只被一个引用(黑色线条)弱引用。整个东西难道不应该被释放吗?

Memory graph debugger showing the object just described

我该如何排除为什么这个东西一直保持着活动状态的问题?

你在setup里面设置了什么吗?每次重新发现那些东西一直保留到所有测试结束,我都感到惊讶。 - matt
没有,根本就没有“设置”。顺便说一下,这个测量是在测量块内进行的,大约是在第10次迭代(共100次)中进行的。 - Alexander
1
那个window.isReleasedWhenClosed = false怎么样?这样会不会导致窗口堆积? - matt
@matt 将其设置为true会导致window.close()崩溃 :| (我弄不清楚为什么)而且这不是窗口堆积(它们在最后的自动释放池中被清除),而是NSOutlineviews(但不包括滚动或剪辑视图)。我应该使用什么工具来跟踪保留/释放?我知道有关分配和泄漏的工具,但我不知道是否有专门跟踪特定对象的保留/释放的工具。这是真的吗? - Alexander
1
太好了,谢谢!我现在正在工作中,等我开始我的个人工作时,我会稍后查看的 :D - Alexander
显示剩余2条评论
1个回答

7
如何调试这个问题一直存在的原因? 使用Instruments。 为了Allocations模板配置Instruments。在开始录制之前,在文件>录制选项下,配置Allocations模板选项以记录引用计数。 录制并暂停。选择要研究的轨道区域。找到想要研究的对象类型并点击小右箭头以显示该类型的所有对象。在列表中选择一个对象。在地址旁边,点击小右箭头。 现在,您将看到保留和释放历史记录以及运行的引用计数。选择一个保留/释放会在右侧显示函数调用堆栈。因此,您可以推断出该对象的内存管理历史记录。 enter image description here

天啊,太不可思议了。我以为分配工具只能追踪第一次的动态内存分配和最终的释放,从来不知道还有这个功能。我知道有一个"录制选项"面板,但我总是忘记去那里查找这样的新功能。 - Alexander
我会保持这个问题开放一段时间,接受新的答案,但稍后会接受这个。现在正在探索这项新能力,太棒了! - Alexander
这些自动保留/释放配对有多可靠?我应该持怀疑态度并验证它们,还是可以直接接受它们作为真实情况? - Alexander
你想知道的只是,是谁留住了你,让你感到意外。编译器不会在ARC配对中出错。 - matt
顺便说一下,在方案编辑器中打开后,内存图还会显示malloc堆栈。但它不会显示完整的保留和释放列表;只有Instruments才能做到这一点。 - matt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接