【问题标题】:RealmSwift List items memory leak problemRealmSwift List items 内存泄漏问题
【发布时间】:2020-01-10 06:33:00
【问题描述】:

我发现在 iOS 中向 Realm 写入大量数据会导致内存不足和崩溃。经过几天的调查,我发现 Realm 不会释放 List 中未使用的对象。我运行了以下示例:

class LeakTestList : Object{
    var items = List<LeakTestItem>()
}

class LeakTestItem : Object{
    @objc dynamic var data = 0
}


func leakTest()
{
    guard let realm = try? Realm() else
    {
        return
    }
    let leakTestList = LeakTestList()
    leakTestList.items.append(objectsIn: (0..<10000).map{LeakTestItem(value: ["data":$0])})
    try? realm.write {
        realm.add(leakTestList)
    }
}

leakTest()返回后,得到如下内存配置文件:

LeakTestList 已经消失,但所有项目都保留在内存中。当我尝试编写大量列表项甚至分成多个足够短的列表时,这会导致内存不足。这是 Realm 的错误还是我可以做些什么来解决这个问题?

【问题讨论】:

  • 不是反对者,但我不确定我是否在关注这个问题。在内存中创建了一堆 Realm 对象,并且假设存在内存泄漏?他们还会在哪里? Realm 对象被延迟加载,这不是代码。
  • @Jay 创建了一堆 Realm 对象,并将其添加到另一个 Realm 对象的 List 中。当函数返回时,不再有对对象的引用。即使包含列表的对象已经被释放,列表项也不会被释放。假设我只有 2GB 的 RAM,并且想编写一个占用 3GB RAM 的领域对象列表。一轮写完是不可能的,但即使我将列表分成更小的部分,仍然是不可能的,因为这些项目永远不会释放。
  • 我使用的是写入磁盘文件的默认领域,而不是纯内存领域。我最近发现我将测试函数放在 viewDidLoad 中,并且列表项领域对象在整个 viewDidLoad 中保持分配,但在 viewDidAppear 中被解除分配。我只是想知道未引用的领域对象何时会完全释放,因为我必须将它们中的很多写入磁盘。
  • 我们已经被这个问题抓住了好几次,所以我想提到该代码写入的文件的大小比它需要的大得多。一方面,使用单个写入事务来写入一堆数据通常比使用一堆添加较小数据的写入事务要好。另一方面,单次写入将创建一个 更大的文件,但速度更快。请参阅@bdash 对this question 的回复。
  • 哦 - 还有一个后续问题;您说当函数返回时,不再有对对象的引用,然后说列表项没有被释放。我运行了您的代码,当泄漏测试函数结束时,没有任何对象-泄漏测试列表和泄漏测试项目仍在内存中。您是说没有对这些对象的引用,但 函数完成后它们仍在内存中?

标签: ios swift memory-leaks realm realm-list


【解决方案1】:

参考@Jay 的回答,我可以从内存监视器中删除领域对象的内存占用,但内存使用量保持不变,直到viewDidLoad() 结束。经过更多的挖掘,结果发现我错过的关键想法是将所有内容包装在autoreleasepool 中。参考这篇文章:https://realm.io/docs/cookbook/swift/object-to-background/

func leakTest()
{
    autoreleasepool {
        guard let realm = try? Realm() else
        {
            return
        }

        let leakTestList = LeakTestList()

        try? realm.write {
            realm.add(leakTestList)
        }

        try? realm.write {
            leakTestList.items.append(objectsIn: (0..<10000).map{LeakTestItem(value: ["data":$0])})
        }
    }
}

【讨论】:

  • 嗨,您确认自动释放池有效吗?我正在进行与您的代码类似的压力测试,并且确实尝试了自动释放,但仍然崩溃。 link
【解决方案2】:

我们发现在领域中建立对象,然后以更小的块附加到该对象似乎不仅有助于解决内存问题,而且还显着减小了文件大小。

我们的项目必须读取和处理 50Gb+ 的文件,我们发现一次写入大约 1000 个对象似乎是速度、文件大小和内存之间的平衡点。您的里程可能会有所不同。

我重构了您的代码并添加了几个 for 循环来显示正在发生的事情,但试试这个,看看内存占用是否相比之下更好。

这会在较小的块中写入总共 10 个项目,正如我在评论中提到的那样,这会减小整体文件大小。对于您的示例,外部循环为 40,内部循环为 1000。

let leakTestList = LeakTestList()

try? realm.write {
    realm.add(leakTestList)
}

var index = 0
for _ in 0..<2 {
    var myItems = [LeakTestItem]()
    for _ in 0..<5 {
        let item = LeakTestItem( value: ["data": index] )
        myItems.append(item)
        index += 1
    }

    try? realm.write {
        leakTestList.items.append(objectsIn: myItems)
    }
}

【讨论】:

  • 感谢您的回复。我测试了这段代码。我在viewDidLoad() 中调用该函数。在函数返回后,领域对象确实从内存配置文件中消失了,但内存使用量在整个函数中不断上升,并且在高使用率下保持不变,直到达到viewWillAppear() 内存使用量下降。我想知道领域是否仅在线程空闲或其他情况下才清理?
  • 我解决了我的问题,方法是在前一个写操作完成后递归地将写操作分派到一个调度队列中,以便在下一个分派项目开始之前完全释放内存。
  • 嗨,Jay,你觉得可以看看这个stackoverflow.com/questions/71167182/…
猜你喜欢
  • 1970-01-01
  • 2016-07-28
相关资源
最近更新 更多