From 7e34b50c640e0a650ea16c81ac872d1583b09ff1 Mon Sep 17 00:00:00 2001 From: kean Date: Sun, 18 Aug 2024 13:49:36 -0400 Subject: [PATCH] Soft-deprecate closure-based APIs --- Nuke.xcodeproj/project.pbxproj | 6 +- .../Pipeline/ImagePipeline+Closures.swift | 107 ++++++++++++++++++ Sources/Nuke/Pipeline/ImagePipeline.swift | 100 +--------------- 3 files changed, 114 insertions(+), 99 deletions(-) create mode 100644 Sources/Nuke/Pipeline/ImagePipeline+Closures.swift diff --git a/Nuke.xcodeproj/project.pbxproj b/Nuke.xcodeproj/project.pbxproj index 6d813d42b..4585de9ca 100644 --- a/Nuke.xcodeproj/project.pbxproj +++ b/Nuke.xcodeproj/project.pbxproj @@ -20,6 +20,7 @@ 0C1453A02657EFA7005E24B3 /* ImagePipelineObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C14539F2657EFA7005E24B3 /* ImagePipelineObserver.swift */; }; 0C1453A12657EFA7005E24B3 /* ImagePipelineObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C14539F2657EFA7005E24B3 /* ImagePipelineObserver.swift */; }; 0C16C85F2C7150C800B2A560 /* ImagePublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C88C578263DAF1E0061A008 /* ImagePublisherTests.swift */; }; + 0C16C8632C726B1B00B2A560 /* ImagePipeline+Closures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C16C8622C726B1B00B2A560 /* ImagePipeline+Closures.swift */; }; 0C179C7B2283597F008AB488 /* ImageEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C179C7A2283597F008AB488 /* ImageEncoding.swift */; }; 0C1B9880294E28D800C09310 /* Nuke.docc in Sources */ = {isa = PBXBuildFile; fileRef = 0C1B987F294E28D800C09310 /* Nuke.docc */; }; 0C1C201D29ABBF19004B38FD /* Nuke.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0C9174901BAE99EE004A7905 /* Nuke.framework */; }; @@ -350,6 +351,7 @@ 0C0FD5D81CA47FE1002A78FB /* ImageProcessing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageProcessing.swift; sourceTree = ""; }; 0C0FD5D91CA47FE1002A78FB /* ImageRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageRequest.swift; sourceTree = ""; }; 0C14539F2657EFA7005E24B3 /* ImagePipelineObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePipelineObserver.swift; sourceTree = ""; }; + 0C16C8622C726B1B00B2A560 /* ImagePipeline+Closures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ImagePipeline+Closures.swift"; sourceTree = ""; }; 0C179C772282AC50008AB488 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .swiftlint.yml; sourceTree = ""; }; 0C179C7A2283597F008AB488 /* ImageEncoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageEncoding.swift; sourceTree = ""; }; 0C1B987F294E28D800C09310 /* Nuke.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = Nuke.docc; sourceTree = ""; }; @@ -952,11 +954,12 @@ isa = PBXGroup; children = ( 0C0FD5D31CA47FE1002A78FB /* ImagePipeline.swift */, - 0CC04B092C5698D500F1164D /* ImagePipelineActor.swift */, 0CF1754B22913F9800A8946E /* ImagePipeline+Configuration.swift */, 0C53C8B0263C968200E62D03 /* ImagePipeline+Delegate.swift */, 0C78A2A6263F4E680051E0FF /* ImagePipeline+Cache.swift */, 0CBA07852852DA8B00CE29F4 /* ImagePipeline+Error.swift */, + 0C16C8622C726B1B00B2A560 /* ImagePipeline+Closures.swift */, + 0CC04B092C5698D500F1164D /* ImagePipelineActor.swift */, ); path = Pipeline; sourceTree = ""; @@ -1729,6 +1732,7 @@ 0CA4ECC426E685F500BAC8E5 /* ImageProcessors+GaussianBlur.swift in Sources */, 0CA4EC9B26E67D3000BAC8E5 /* ImageDecoders+Empty.swift in Sources */, 0CB26802208F2565004C83F4 /* DataCache.swift in Sources */, + 0C16C8632C726B1B00B2A560 /* ImagePipeline+Closures.swift in Sources */, 0CA4EC9F26E67D6200BAC8E5 /* ImageDecoderRegistry.swift in Sources */, 0CA4ECBA26E6850B00BAC8E5 /* Graphics.swift in Sources */, 0CA4ECB426E6844B00BAC8E5 /* ImageProcessors.swift in Sources */, diff --git a/Sources/Nuke/Pipeline/ImagePipeline+Closures.swift b/Sources/Nuke/Pipeline/ImagePipeline+Closures.swift new file mode 100644 index 000000000..c041daf8e --- /dev/null +++ b/Sources/Nuke/Pipeline/ImagePipeline+Closures.swift @@ -0,0 +1,107 @@ +// The MIT License (MIT) +// +// Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean). + +import Foundation + +extension ImagePipeline { + /// Loads an image for the given request. + /// + /// - warning: Soft-deprecated in Nuke 13.0. + /// + /// - parameters: + /// - request: An image request. + /// - completion: A closure to be called on the main thread when the request + /// is finished. + @discardableResult public nonisolated func loadImage( + with url: URL, + completion: @MainActor @Sendable @escaping (_ result: Result) -> Void + ) -> ImageTask { + _loadImage(with: ImageRequest(url: url), progress: nil, completion: completion) + } + + /// Loads an image for the given request. + /// + /// - warning: Soft-deprecated in Nuke 13.0. + /// + /// - parameters: + /// - request: An image request. + /// - completion: A closure to be called on the main thread when the request + /// is finished. + @discardableResult public nonisolated func loadImage( + with request: ImageRequest, + completion: @MainActor @Sendable @escaping (_ result: Result) -> Void + ) -> ImageTask { + _loadImage(with: request, progress: nil, completion: completion) + } + + /// Loads an image for the given request. + /// + /// - warning: Soft-deprecated in Nuke 13.0. + /// + /// - parameters: + /// - request: An image request. + /// - progress: A closure to be called periodically on the main thread when + /// the progress is updated. + /// - completion: A closure to be called on the main thread when the request + /// is finished. + @discardableResult public nonisolated func loadImage( + with request: ImageRequest, + progress: (@MainActor @Sendable (_ response: ImageResponse?, _ completed: Int64, _ total: Int64) -> Void)?, + completion: @MainActor @Sendable @escaping (_ result: Result) -> Void + ) -> ImageTask { + _loadImage(with: request, progress: { + progress?($0, $1.completed, $1.total) + }, completion: completion) + } + + /// Loads the image data for the given request. The data doesn't get decoded + /// or processed in any other way. + /// + /// You can call ``loadImage(with:completion:)-43osv`` for the request at any point after calling + /// ``loadData(with:completion:)-6cwk3``, the pipeline will use the same operation to load the data, + /// no duplicated work will be performed. + /// + /// - warning: Soft-deprecated in Nuke 13.0. + /// + /// - parameters: + /// - request: An image request. + /// - progress: A closure to be called periodically on the main thread when the progress is updated. + /// - completion: A closure to be called on the main thread when the request is finished. + @discardableResult public nonisolated func loadData( + with request: ImageRequest, + progress progressHandler: (@MainActor @Sendable (_ completed: Int64, _ total: Int64) -> Void)? = nil, + completion: @MainActor @Sendable @escaping (Result<(data: Data, response: URLResponse?), Error>) -> Void + ) -> ImageTask { + _loadImage(with: request, isDataTask: true) { _, progress in + progressHandler?(progress.completed, progress.total) + } completion: { result in + let result = result.map { response in + // Data should never be empty + (data: response.container.data ?? Data(), response: response.urlResponse) + } + completion(result) + } + } + + private nonisolated func _loadImage( + with request: ImageRequest, + isDataTask: Bool = false, + progress: (@MainActor @Sendable (ImageResponse?, ImageTask.Progress) -> Void)?, + completion: @MainActor @Sendable @escaping (Result) -> Void + ) -> ImageTask { + makeImageTask(with: request, isDataTask: isDataTask) { event, task in + DispatchQueue.main.async { + // The callback-based API guarantees that after cancellation no + // event are called on the callback queue. + guard !task.isCancelling else { return } + switch event { + case .progress(let value): progress?(nil, value) + case .preview(let response): progress?(response, task.currentProgress) + case .cancelled: break // The legacy APIs do not send cancellation events + case .finished(let result): completion(result) + } + } + } + } +} diff --git a/Sources/Nuke/Pipeline/ImagePipeline.swift b/Sources/Nuke/Pipeline/ImagePipeline.swift index 8bb528e96..c738ffd04 100644 --- a/Sources/Nuke/Pipeline/ImagePipeline.swift +++ b/Sources/Nuke/Pipeline/ImagePipeline.swift @@ -101,7 +101,7 @@ public final class ImagePipeline { } } - // MARK: - Loading Images (Async/Await) + // MARK: - Loading Images /// Creates a task with the given URL. /// @@ -127,7 +127,7 @@ public final class ImagePipeline { try await imageTask(with: request).image } - // MARK: - Loading Data (Async/Await) + // MARK: - Loading Data /// Returns image data for the given request. /// @@ -138,102 +138,6 @@ public final class ImagePipeline { return (response.container.data ?? Data(), response.urlResponse) } - // MARK: - Loading Images (Closures) - - /// Loads an image for the given request. - /// - /// - parameters: - /// - request: An image request. - /// - completion: A closure to be called on the main thread when the request - /// is finished. - @discardableResult public nonisolated func loadImage( - with url: URL, - completion: @MainActor @Sendable @escaping (_ result: Result) -> Void - ) -> ImageTask { - _loadImage(with: ImageRequest(url: url), progress: nil, completion: completion) - } - - /// Loads an image for the given request. - /// - /// - parameters: - /// - request: An image request. - /// - completion: A closure to be called on the main thread when the request - /// is finished. - @discardableResult public nonisolated func loadImage( - with request: ImageRequest, - completion: @MainActor @Sendable @escaping (_ result: Result) -> Void - ) -> ImageTask { - _loadImage(with: request, progress: nil, completion: completion) - } - - /// Loads an image for the given request. - /// - /// - parameters: - /// - request: An image request. - /// - progress: A closure to be called periodically on the main thread when - /// the progress is updated. - /// - completion: A closure to be called on the main thread when the request - /// is finished. - @discardableResult public nonisolated func loadImage( - with request: ImageRequest, - progress: (@MainActor @Sendable (_ response: ImageResponse?, _ completed: Int64, _ total: Int64) -> Void)?, - completion: @MainActor @Sendable @escaping (_ result: Result) -> Void - ) -> ImageTask { - _loadImage(with: request, progress: { - progress?($0, $1.completed, $1.total) - }, completion: completion) - } - - private nonisolated func _loadImage( - with request: ImageRequest, - isDataTask: Bool = false, - progress: (@MainActor @Sendable (ImageResponse?, ImageTask.Progress) -> Void)?, - completion: @MainActor @Sendable @escaping (Result) -> Void - ) -> ImageTask { - makeImageTask(with: request, isDataTask: isDataTask) { event, task in - DispatchQueue.main.async { - // The callback-based API guarantees that after cancellation no - // event are called on the callback queue. - guard !task.isCancelling else { return } - switch event { - case .progress(let value): progress?(nil, value) - case .preview(let response): progress?(response, task.currentProgress) - case .cancelled: break // The legacy APIs do not send cancellation events - case .finished(let result): completion(result) - } - } - } - } - - // MARK: - Loading Data (Closures) - - /// Loads the image data for the given request. The data doesn't get decoded - /// or processed in any other way. - /// - /// You can call ``loadImage(with:completion:)-43osv`` for the request at any point after calling - /// ``loadData(with:completion:)-6cwk3``, the pipeline will use the same operation to load the data, - /// no duplicated work will be performed. - /// - /// - parameters: - /// - request: An image request. - /// - progress: A closure to be called periodically on the main thread when the progress is updated. - /// - completion: A closure to be called on the main thread when the request is finished. - @discardableResult public nonisolated func loadData( - with request: ImageRequest, - progress progressHandler: (@MainActor @Sendable (_ completed: Int64, _ total: Int64) -> Void)? = nil, - completion: @MainActor @Sendable @escaping (Result<(data: Data, response: URLResponse?), Error>) -> Void - ) -> ImageTask { - _loadImage(with: request, isDataTask: true) { _, progress in - progressHandler?(progress.completed, progress.total) - } completion: { result in - let result = result.map { response in - // Data should never be empty - (data: response.container.data ?? Data(), response: response.urlResponse) - } - completion(result) - } - } - // MARK: - ImageTask (Internal) nonisolated func makeImageTask(with request: ImageRequest, isDataTask: Bool = false, onEvent: ((ImageTask.Event, ImageTask) -> Void)? = nil) -> ImageTask {