使用 URLSessionDataTask 和 @Published 的内存泄漏

问题描述

我在我的 SwiftUI 应用程序中有这个 APIRepresentative 类作为单例 EnvironmentObject。该类具有 @Published var,它按 ID 保存所有 InsightData 结构。

即使在不依赖于 insightData 的视图中,重复调用 getInsightData 函数也会导致我的应用中的内存使用量每次增加大约 200Kb 左右。在我的应用的整个生命周期中,这将导致内存使用量激增至数 GB。

关键在于:当我删除 @PublishedinsightData 修饰符时,内存泄漏消失了。然后我可以随心所欲地调用我的函数,而不会增加内存使用量。知道为什么会这样吗?我非常想保留 @Published 属性

import Foundation
import Combine

final class APIRepresentative: ObservableObject {
    private static let baseURLString = "https://apptelemetry.io/api/v1/"

    @Published var insightData: [UUID: InsightDataTransferObject] = [:]

    // More published properties
    // ...
}


extension APIRepresentative {
    func getInsightData(for insight: Insight,in insightGroup: InsightGroup,in app: TelemetryApp,callback: ((Result<InsightDataTransferObject,TransferError>) -> ())? = nil) {
        let timeWindowEndDate = timeWindowEnd ?? Date()
        let timeWindowBeginDate = timeWindowBeginning ?? timeWindowEndDate.addingTimeInterval(-60 * 60 * 24 * 30)

        let url = urlForPath("apps",app.id.uuidString,"insightgroups",insightGroup.id.uuidString,"insights",insight.id.uuidString,Formatter.iso8601noFS.string(from: timeWindowBeginDate),Formatter.iso8601noFS.string(from: timeWindowEndDate)
        )

        let request = self.authenticatedURLRequest(for: url,httpMethod: "GET")
        URLSession.shared.dataTask(with: request) { data,response,error in
            if let data = data {
                let decoded = try! JSONDecoder.telemetryDecoder.decode(InsightDataTransferObject.self,from: data)

                dispatchQueue.main.async { [weak self] in
                    guard let self = self else {
                        print("Self is gone")
                        return
                    }

                    var newInsightData = self.insightData
                    newInsightData[decoded.id] = decoded

                    self.insightData = newInsightData
                }
            }
        }.resume()
    }
}

// more retrieval functions for the other published properties
// ...

这是full file,但我很确定我在这个精简版中包含了所有相关部分

解决方法

首先,我假设内存会随着这条线的增长而增长:

        self.insightData[decoded.id] = decoded

您为每次下载都存储了新值,并且除非重复 decoded.id,否则似乎永远不会释放它们。这不是泄漏。那只是在内存中存储更多数据。

也就是说,如果您使用 while 循环对此进行测试,您应该预期内存会大幅增长,因为您永远不会耗尽自动释放池。当前运行循环周期完成时,自动释放池会被排空,但此 while 循环永远不会结束。所以你想创建一个新池:

while true {
    @autoreleasepool { 
        api.getInsightData(for: insight,in: insightGroup,in: app)
        sleep(1)
    }
}