|
|
@@ -53,6 +53,10 @@ public class ImagePrefetcher {
|
|
|
/// The maximum concurrent downloads to use when prefetching images. Default is 5.
|
|
|
public var maxConcurrentDownloads = 5
|
|
|
|
|
|
+ /// The dispatch queue to use for handling resource process so downloading does not occur on the main thread
|
|
|
+ /// This prevents stuttering when preloading images in a collection view or table view
|
|
|
+ private var prefetchQueue: DispatchQueue
|
|
|
+
|
|
|
private let prefetchResources: [Resource]
|
|
|
private let optionsInfo: KingfisherOptionsInfo
|
|
|
private var progressBlock: PrefetcherProgressBlock?
|
|
|
@@ -123,9 +127,17 @@ public class ImagePrefetcher {
|
|
|
prefetchResources = resources
|
|
|
pendingResources = ArraySlice(resources)
|
|
|
|
|
|
- // We want all callbacks from main queue, so we ignore the call back queue in options
|
|
|
- let optionsInfoWithoutQueue = options?.removeAllMatchesIgnoringAssociatedValue(.callbackDispatchQueue(nil))
|
|
|
- self.optionsInfo = optionsInfoWithoutQueue ?? KingfisherEmptyOptionsInfo
|
|
|
+ // Set up the dispatch queue that all our work should occur on.
|
|
|
+ let prefetchQueueName = "com.onevcat.Kingfisher.PrefetchQueue"
|
|
|
+ prefetchQueue = DispatchQueue(label: prefetchQueueName)
|
|
|
+
|
|
|
+ // We want all callbacks from our prefetch queue, so we should ignore the call back queue in options
|
|
|
+ var optionsInfoWithoutQueue = options?.removeAllMatchesIgnoringAssociatedValue(.callbackDispatchQueue(nil)) ?? KingfisherEmptyOptionsInfo
|
|
|
+
|
|
|
+ // Add our own callback dispatch queue to make sure all callbacks are coming back in our expected queue
|
|
|
+ optionsInfoWithoutQueue.append(.callbackDispatchQueue(prefetchQueue))
|
|
|
+
|
|
|
+ self.optionsInfo = optionsInfoWithoutQueue
|
|
|
|
|
|
let cache = self.optionsInfo.targetCache
|
|
|
let downloader = self.optionsInfo.downloader
|
|
|
@@ -142,8 +154,8 @@ public class ImagePrefetcher {
|
|
|
*/
|
|
|
public func start()
|
|
|
{
|
|
|
- // Since we want to handle the resources cancellation in main thread only.
|
|
|
- DispatchQueue.main.safeAsync {
|
|
|
+ // Since we want to handle the resources cancellation in the prefetch queue only.
|
|
|
+ prefetchQueue.async {
|
|
|
|
|
|
guard !self.stopped else {
|
|
|
assertionFailure("You can not restart the same prefetcher. Try to create a new prefetcher.")
|
|
|
@@ -176,7 +188,7 @@ public class ImagePrefetcher {
|
|
|
Stop current downloading progress, and cancel any future prefetching activity that might be occuring.
|
|
|
*/
|
|
|
public func stop() {
|
|
|
- DispatchQueue.main.safeAsync {
|
|
|
+ prefetchQueue.async {
|
|
|
if self.finished { return }
|
|
|
self.stopped = true
|
|
|
self.tasks.values.forEach { $0.cancel() }
|
|
|
@@ -244,7 +256,7 @@ public class ImagePrefetcher {
|
|
|
}
|
|
|
|
|
|
func reportCompletionOrStartNext() {
|
|
|
- DispatchQueue.main.async {
|
|
|
+ prefetchQueue.async {
|
|
|
if let resource = self.pendingResources.popFirst() {
|
|
|
self.startPrefetching(resource)
|
|
|
} else {
|
|
|
@@ -255,8 +267,11 @@ public class ImagePrefetcher {
|
|
|
}
|
|
|
|
|
|
func handleComplete() {
|
|
|
- completionHandler?(skippedResources, failedResources, completedResources)
|
|
|
- completionHandler = nil
|
|
|
- progressBlock = nil
|
|
|
+ // The completion handler should be called on the main thread
|
|
|
+ DispatchQueue.main.safeAsync {
|
|
|
+ self.completionHandler?(self.skippedResources, self.failedResources, self.completedResources)
|
|
|
+ self.completionHandler = nil
|
|
|
+ self.progressBlock = nil
|
|
|
+ }
|
|
|
}
|
|
|
}
|