Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 31 additions & 7 deletions SDWebImageSwiftUI/Classes/ImageManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,27 @@ public final class ImageManager : ObservableObject {

var currentURL: URL?
var transaction = Transaction()
var successBlock: ((PlatformImage, Data?, SDImageCacheType) -> Void)?
var failureBlock: ((Error) -> Void)?
var progressBlock: ((Int, Int) -> Void)?

// Thread-safe callback properties
private let callbackQueue = DispatchQueue(label: "ImageManager.callbacks", qos: .userInitiated)
Copy link
Collaborator

@dreampiggy dreampiggy Sep 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ImageManager is not a shared instance. I remember it's just behaved as ObservedObject used for View (it like a ViewModel), so it's created everytime new URL is provided.

Does this (means, you create each DispatchQueue in each Manager instance) cause queue explosion ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid point!
How do you propose I fix it?

private var _successBlock: ((PlatformImage, Data?, SDImageCacheType) -> Void)?
private var _failureBlock: ((Error) -> Void)?
private var _progressBlock: ((Int, Int) -> Void)?

var successBlock: ((PlatformImage, Data?, SDImageCacheType) -> Void)? {
get { callbackQueue.sync { _successBlock } }
set { callbackQueue.sync { _successBlock = newValue } }
}

var failureBlock: ((Error) -> Void)? {
get { callbackQueue.sync { _failureBlock } }
set { callbackQueue.sync { _failureBlock = newValue } }
}

var progressBlock: ((Int, Int) -> Void)? {
get { callbackQueue.sync { _progressBlock } }
set { callbackQueue.sync { _progressBlock = newValue } }
}

public init() {}

Expand Down Expand Up @@ -96,9 +114,11 @@ public final class ImageManager : ObservableObject {
progress = 0
}
self.indicatorStatus.progress = progress
if let progressBlock = self.progressBlock {
// Capture progress callback in thread-safe way
let progressCallback = self.progressBlock
if let progressCallback = progressCallback {
DispatchQueue.main.async {
progressBlock(receivedSize, expectedSize)
progressCallback(receivedSize, expectedSize)
}
}
}) { [weak self] (image, data, error, cacheType, finished, _) in
Expand All @@ -112,6 +132,10 @@ public final class ImageManager : ObservableObject {
// So previous View struct call `onDisappear` and cancel the currentOperation
return
}
// Capture completion callbacks in thread-safe way
let successCallback = self.successBlock
let failureCallback = self.failureBlock

withTransaction(self.transaction) {
self.image = image
self.error = error
Expand All @@ -122,9 +146,9 @@ public final class ImageManager : ObservableObject {
self.indicatorStatus.isLoading = false
self.indicatorStatus.progress = 1
if let image = image {
self.successBlock?(image, data, cacheType)
successCallback?(image, data, cacheType)
} else {
self.failureBlock?(error ?? NSError())
failureCallback?(error ?? NSError())
}
}
}
Expand Down
Loading