Reputation: 21572
I have this APIRepresentative class as a singleton EnvironmentObject
in my SwiftUI app. The class has @Published var
that holds all InsightData
structs by ID.
Even in a view that has no dependencies on insightData
, repeatedly calling the getInsightData
function causes memory usage in my app to rise by about 200Kb or so each time. Over the lifetime of my app, this will cause memory usage to balloon to several gigabytes.
Here's the kicker: The memory leak vanishes when I remove the @Published
modifier for insightData
. I can then call my function as much as I like, with no increase in memory usage. Any idea why that is the case? I would very much like to keep the @Published
property.
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
// ...
Here's the full file but I'm pretty sure I included all relevant parts in this whittled down version
Upvotes: 3
Views: 454
Reputation: 299275
First, I would assume that memory would grow given this line:
self.insightData[decoded.id] = decoded
You're storing new values for every download, and don't seem to ever release them unless decoded.id
repeats. That's not a leak; that's just storing more data in memory.
That said, if you're testing this with your while
loop, you should expect substantial memory growth because you never drain the autorelease pool. The autorelease pool is drained when the current run loop cycle completes, but this while
loop never ends. So you'd want to create a new pool:
while true {
@autoreleasepool {
api.getInsightData(for: insight, in: insightGroup, in: app)
sleep(1)
}
}
Upvotes: 1