From 39c537eee91f30e8cbda96ab2dc6404d49e4bfc3 Mon Sep 17 00:00:00 2001 From: kean Date: Tue, 23 Apr 2024 20:58:45 -0400 Subject: [PATCH 1/3] Add isLocalResourcesSupportEnabled --- .../Pipeline/ImagePipelineConfiguration.swift | 4 ++ .../Nuke/Tasks/TaskFetchOriginalData.swift | 12 +++++- .../ImagePipelineDataCacheTests.swift | 39 ++++++++++++++----- 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/Sources/Nuke/Pipeline/ImagePipelineConfiguration.swift b/Sources/Nuke/Pipeline/ImagePipelineConfiguration.swift index d13447df8..64e27b912 100644 --- a/Sources/Nuke/Pipeline/ImagePipelineConfiguration.swift +++ b/Sources/Nuke/Pipeline/ImagePipelineConfiguration.swift @@ -114,6 +114,10 @@ extension ImagePipeline { /// `Last-Modified`). Resumable downloads are enabled by default. public var isResumableDataEnabled = true + /// If enabled, the pipeline will load the local resources (`file` and + /// `data` schemes) inline without using the data loader. By default, `true`. + public var isLocalResourcesSupportEnabled = true + /// A queue on which all callbacks, like `progress` and `completion` /// callbacks are called. `.main` by default. public var callbackQueue = DispatchQueue.main diff --git a/Sources/Nuke/Tasks/TaskFetchOriginalData.swift b/Sources/Nuke/Tasks/TaskFetchOriginalData.swift index 0f59103e6..add602a62 100644 --- a/Sources/Nuke/Tasks/TaskFetchOriginalData.swift +++ b/Sources/Nuke/Tasks/TaskFetchOriginalData.swift @@ -13,12 +13,22 @@ final class TaskFetchOriginalData: ImagePipelineTask<(Data, URLResponse?)> { private var data = Data() override func start() { - guard let urlRequest = request.urlRequest else { + guard let urlRequest = request.urlRequest, let url = urlRequest.url else { // A malformed URL prevented a URL request from being initiated. send(error: .dataLoadingFailed(error: URLError(.badURL))) return } + if url.isLocalResource { + do { + let data = try Data(contentsOf: url) + send(value: (data, nil), isCompleted: true) + } catch { + send(error: .dataLoadingFailed(error: error)) + } + return + } + if let rateLimiter = pipeline.rateLimiter { // Rate limiter is synchronized on pipeline's queue. Delayed work is // executed asynchronously also on the same queue. diff --git a/Tests/NukeTests/ImagePipelineTests/ImagePipelineDataCacheTests.swift b/Tests/NukeTests/ImagePipelineTests/ImagePipelineDataCacheTests.swift index a2c042c1e..2ad079646 100644 --- a/Tests/NukeTests/ImagePipelineTests/ImagePipelineDataCacheTests.swift +++ b/Tests/NukeTests/ImagePipelineTests/ImagePipelineDataCacheTests.swift @@ -624,8 +624,8 @@ class ImagePipelineDataCachePolicyTests: XCTestCase { XCTAssertEqual(dataCache.store.count, 2) } - // MARK: Local Storage - + // MARK: Local Resources + func testImagesFromLocalStorageNotCached() { // GIVEN pipeline = pipeline.reconfigured { @@ -633,8 +633,8 @@ class ImagePipelineDataCachePolicyTests: XCTestCase { } // GIVEN request without a processor - let request = ImageRequest(url: URL(string: "file://example/image.jpeg")) - + let request = ImageRequest(url: Test.url(forResource: "fixture", extension: "jpeg")) + // WHEN expect(pipeline).toLoadImage(with: request) wait() @@ -652,8 +652,8 @@ class ImagePipelineDataCachePolicyTests: XCTestCase { } // GIVEN request with a processor - let request = ImageRequest(url: URL(string: "file://example/image.jpeg") ,processors: [.resize(width: 100)]) - + let request = ImageRequest(url: Test.url(forResource: "fixture", extension: "jpeg") ,processors: [.resize(width: 100)]) + // WHEN expect(pipeline).toLoadImage(with: request) wait() @@ -671,8 +671,8 @@ class ImagePipelineDataCachePolicyTests: XCTestCase { } // GIVEN request without a processor - let request = ImageRequest(url: URL(string: "data://example/image.jpeg")) - + let request = ImageRequest(url: Test.url(forResource: "fixture", extension: "jpeg")) + // WHEN expect(pipeline).toLoadImage(with: request) wait() @@ -682,7 +682,28 @@ class ImagePipelineDataCachePolicyTests: XCTestCase { XCTAssertEqual(dataCache.writeCount, 0) XCTAssertEqual(dataCache.store.count, 0) } - + + func testImagesFromData() { + // GIVEN + pipeline = pipeline.reconfigured { + $0.dataCachePolicy = .automatic + } + + // GIVEN request without a processor + let data = Test.data(name: "fixture", extension: "jpeg") + let url = URL(string: "data:image/jpeg;base64,\(data.base64EncodedString())") + let request = ImageRequest(url: url) + + // WHEN + expect(pipeline).toLoadImage(with: request) + wait() + + // THEN original image data is stored in disk cache + XCTAssertEqual(encoder.encodeCount, 0) + XCTAssertEqual(dataCache.writeCount, 0) + XCTAssertEqual(dataCache.store.count, 0) + } + // MARK: Misc func testSetCustomImageEncoder() { From ac3e2511ea4b75247a9e82918f530d38d6c21726 Mon Sep 17 00:00:00 2001 From: kean Date: Tue, 23 Apr 2024 21:02:04 -0400 Subject: [PATCH 2/3] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2ae66b51..b6ebd96f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - Fix https://github.com/kean/Nuke/issues/763 SwiftUI Warning: Accessing StateObject's object without being installed on a View when using `onStart` - Fix https://github.com/kean/Nuke/issues/758 by adding support for initializing `ImageProcessors.CoreImageFilter` with `CIFilter` instances - Add support for disk cache lookup for intermediate processed images (as opposed to only final and original as before) +- Add an optimization that loads local resources with `file` and `data` schemes quickly without using `DataLoader` and `URLSession`. If you rely on the existing behavior, this optimization can be turned off using the `isLocalResourcesSupportEnabled` configuration option. https://github.com/kean/Nuke/pull/779 - Deprecate `ImagePipeline.Configuration.dataCachingQueue` and perform data cache lookups on the pipeline's queue, reducing the amount of context switching - Update the infrastructure for coalescing image-processing tasks to use the task-dependency used for other operations From 800e86d46ea1fed25f6b299a93aa6af4873fc046 Mon Sep 17 00:00:00 2001 From: kean Date: Tue, 23 Apr 2024 21:02:51 -0400 Subject: [PATCH 3/3] Add isLocalResourcesSupportEnabled support --- Sources/Nuke/Tasks/TaskFetchOriginalData.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Nuke/Tasks/TaskFetchOriginalData.swift b/Sources/Nuke/Tasks/TaskFetchOriginalData.swift index add602a62..53200ea8f 100644 --- a/Sources/Nuke/Tasks/TaskFetchOriginalData.swift +++ b/Sources/Nuke/Tasks/TaskFetchOriginalData.swift @@ -19,7 +19,7 @@ final class TaskFetchOriginalData: ImagePipelineTask<(Data, URLResponse?)> { return } - if url.isLocalResource { + if url.isLocalResource && pipeline.configuration.isLocalResourcesSupportEnabled { do { let data = try Data(contentsOf: url) send(value: (data, nil), isCompleted: true)