【问题标题】:Swift How to solve a Memory "Leak"Swift 如何解决内存“泄漏”
【发布时间】:2021-11-18 00:00:18
【问题描述】:

我遇到了我不理解的内存“泄漏”。我有一个缓存类,其目的是限制重型数据项的实例数量。即使它存储了这些项目的 NO 个实例,它们也会以某种方式被保留。如果它们来自后台线程,我相信它们会在任务完成后被释放。但是,当然,正如代码所示,它们在主线程上仍然存在。

这发生在 iPadOS 14.8、iPadOS 15 和 MacCatalyst 上。

我遗漏了什么或不明白什么?

class DataCache {
    var id: String
    var data: Data? {
        get {
            let url = FileManager.default.temporaryDirectory
                .appendingPathComponent("\(id).txt")
            return try! Data(contentsOf: url)
        }
        
        set {
            let url = FileManager.default.temporaryDirectory
                .appendingPathComponent("\(id).txt")
            try! newValue!.write(to: url)
        }
    }

    init(id: String, data: Data) {
        self.id = id
        self.data = data
    }
}
class Item  {
    static var selection = [Item]()
    static var items = [String:Item]()
    
    var id: String = UUID().uuidString
    var itemData: DataCache
    
    init() {
        itemData = DataCache(id: id,
                            data: Data(String(repeating: "dummy", count: 4_000_000).utf8)
                            )
    }
    
    required init(_ other: Item) {
        self.itemData = DataCache(id: self.id, data: other.itemData.data!)
    }
    
    func duplicate(times: Int) {
        for index in 0..<times {
            print(index)
            Item.selection.append(Item(self))
        }
    }
    
}

@main struct Main {
    static func main() throws {
        let item = Item()
        
        perform(item)
        
        performOnSelection() { item in
            let _ = Item(item)
        }
        
        while (true) {}
    }
    
    static func perform(_ item: Item) {
        item.duplicate(times: 100)
    }
    
    static func performOnSelection(perform action: @escaping (Item)->Void) {
        var done = false
        DispatchQueue.global().async {
            for item in Item.selection {
                action(item)
            }
            done = true
        }
        
        while !done { sleep (1) }
    }
}

【问题讨论】:

  • 您可以使用 Xcode 中的内存图表工具查看谁在保留对“泄露”对象的引用。
  • 完全不相关,但是如果你只是阻塞调用线程直到 done 设置为 true,那么调度到全局队列是没有意义的。 (顺便说一句,done 的更新不是线程安全的。)
  • 这只是一个最小的、可重现的例子。它实际上来自一个不需要完成和等待的 SwiftUI 应用程序。在这里,我只是添加了它们,以便有时间检查结果。我离开了全局队列以显示这些项目正在被释放,所以并不是真正的泄漏。但感谢您的评论和快速回答。

标签: ios swift caching memory-leaks


【解决方案1】:

您正在创建自动释放对象。插入autoreleasepool 以定期排空池,例如:

func performOnSelection(perform action: @escaping (Item) -> Void) {
    ...

    autoreleasepool {
        perform(item)
    }

    performOnSelection() { item in
        autoreleasepool {
            let _ = Item(item)
        }
    }

    ...
}

func duplicate(times: Int) {
    for index in 0..<times {
        print(index)
        autoreleasepool { [self] in
            Item.selection.append(Item(self))
        }
    }
}

例如没有autoreleasepool:

还有:

当它不处理所有那些挥之不去的自动释放对象时,它也会变得更快。峰值内存使用量已从 3.4gb 变为 47mb。

【讨论】:

  • 太棒了!它就像一个魅力!我依稀记得这些来自 Objective-C。不知道它们可用于 Swift。
  • 通常我们不必在 Swift 代码中担心它,但您使用的是 Swift Foundation 类型,它们在幕后桥接了遗留的 Objective-C API。每当您看到内存没有在长循环中释放时,自动释放对象就是常见的罪魁祸首。
猜你喜欢
  • 2015-06-28
  • 1970-01-01
  • 1970-01-01
  • 2018-04-11
  • 2020-04-07
  • 1970-01-01
  • 2013-09-02
相关资源
最近更新 更多