From ab66c91fc5c471b3773b2fb581b51cfac3d64fa0 Mon Sep 17 00:00:00 2001 From: Hazem Ali Date: Wed, 6 Nov 2024 10:14:09 +0200 Subject: [PATCH] Fix retain cycles Resolved retain cycles by capturing self weakly in async closure and Combine sink subscription. This prevents Downloader from being held in memory due to strong references in URLSession and Combine. --- Diffusion/Common/Downloader.swift | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Diffusion/Common/Downloader.swift b/Diffusion/Common/Downloader.swift index b8cd9cd..5412d36 100644 --- a/Diffusion/Common/Downloader.swift +++ b/Diffusion/Common/Downloader.swift @@ -38,28 +38,33 @@ class Downloader: NSObject, ObservableObject { #endif urlSession = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue()) downloadState.value = .downloading(0) - urlSession?.getAllTasks { tasks in - // If there's an existing pending background task with the same URL, let it proceed. + urlSession?.getAllTasks { [weak self] tasks in + // Avoiding retain cycles by capturing weak self in stead of self. + guard let weakSelf = self else { return } guard tasks.filter({ $0.originalRequest?.url == url }).isEmpty else { print("Already downloading \(url)") return } print("Starting download of \(url)") - + var request = URLRequest(url: url) if let authToken = authToken { request.setValue("Bearer \(authToken)", forHTTPHeaderField: "Authorization") } - - self.urlSession?.downloadTask(with: request).resume() + // if we used strong self, retain cycles may occur, especially in async network calls. + // To avoid that, we should use weak self. + weakSelf.urlSession?.downloadTask(with: request).resume() } + } @discardableResult func waitUntilDone() throws -> URL { // It's either this, or stream the bytes ourselves (add to a buffer, save to disk, etc; boring and finicky) let semaphore = DispatchSemaphore(value: 0) - stateSubscriber = downloadState.sink { state in + stateSubscriber = downloadState.sink { [weak self] state in + // Without [weak self], this could create a retain cycle, especially since sink maintains a strong reference to self for the duration of the subscription. + guard let weakSelf = self else { return } switch state { case .completed: semaphore.signal() case .failed: semaphore.signal()