Skip to content

Commit

Permalink
Cleanup ImageRequestKeys
Browse files Browse the repository at this point in the history
  • Loading branch information
kean committed Apr 21, 2024
1 parent 7d7efed commit 1f1fc21
Show file tree
Hide file tree
Showing 11 changed files with 96 additions and 126 deletions.
16 changes: 8 additions & 8 deletions Nuke.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,8 @@
0CB26807208F25C2004C83F4 /* DataCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB26806208F25C2004C83F4 /* DataCacheTests.swift */; };
0CB2EFD22110F38600F7C63F /* ImagePipelineConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB2EFD12110F38600F7C63F /* ImagePipelineConfigurationTests.swift */; };
0CB2EFD62110F52C00F7C63F /* RateLimiterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB2EFD52110F52C00F7C63F /* RateLimiterTests.swift */; };
0CB402D525B6569700F5A241 /* TaskFetchOriginalImageData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB402D425B6569700F5A241 /* TaskFetchOriginalImageData.swift */; };
0CB402DB25B656D200F5A241 /* TaskFetchDecodedImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB402DA25B656D200F5A241 /* TaskFetchDecodedImage.swift */; };
0CB402D525B6569700F5A241 /* TaskFetchOriginalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB402D425B6569700F5A241 /* TaskFetchOriginalData.swift */; };
0CB402DB25B656D200F5A241 /* TaskFetchOriginalImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB402DA25B656D200F5A241 /* TaskFetchOriginalImage.swift */; };
0CB4030125B6639200F5A241 /* TaskLoadImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB4030025B6639200F5A241 /* TaskLoadImage.swift */; };
0CB6448928567DC300916267 /* MockImageProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7C068A1BCA888800089D7F /* MockImageProcessor.swift */; };
0CB6448A28567DC300916267 /* MockDataLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7C068C1BCA888800089D7F /* MockDataLoader.swift */; };
Expand Down Expand Up @@ -493,8 +493,8 @@
0CB26806208F25C2004C83F4 /* DataCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataCacheTests.swift; sourceTree = "<group>"; };
0CB2EFD12110F38600F7C63F /* ImagePipelineConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePipelineConfigurationTests.swift; sourceTree = "<group>"; };
0CB2EFD52110F52C00F7C63F /* RateLimiterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RateLimiterTests.swift; sourceTree = "<group>"; };
0CB402D425B6569700F5A241 /* TaskFetchOriginalImageData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskFetchOriginalImageData.swift; sourceTree = "<group>"; };
0CB402DA25B656D200F5A241 /* TaskFetchDecodedImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskFetchDecodedImage.swift; sourceTree = "<group>"; };
0CB402D425B6569700F5A241 /* TaskFetchOriginalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskFetchOriginalData.swift; sourceTree = "<group>"; };
0CB402DA25B656D200F5A241 /* TaskFetchOriginalImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskFetchOriginalImage.swift; sourceTree = "<group>"; };
0CB4030025B6639200F5A241 /* TaskLoadImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskLoadImage.swift; sourceTree = "<group>"; };
0CB6449928567DE000916267 /* NukeExtensionsTestsHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NukeExtensionsTestsHelpers.swift; sourceTree = "<group>"; };
0CB6449B28567E5400916267 /* ImageViewLoadingOptionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewLoadingOptionsTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -969,8 +969,8 @@
0C2CD6EA25B67FB30017018F /* ImagePipelineTask.swift */,
0CB4030025B6639200F5A241 /* TaskLoadImage.swift */,
0C2A368A26437BF100F1D000 /* TaskLoadData.swift */,
0CB402DA25B656D200F5A241 /* TaskFetchDecodedImage.swift */,
0CB402D425B6569700F5A241 /* TaskFetchOriginalImageData.swift */,
0CB402DA25B656D200F5A241 /* TaskFetchOriginalImage.swift */,
0CB402D425B6569700F5A241 /* TaskFetchOriginalData.swift */,
0CE6202226543B6A00AAB8C3 /* TaskFetchWithPublisher.swift */,
);
path = Tasks;
Expand Down Expand Up @@ -1732,7 +1732,7 @@
0CA4ECD326E68FDC00BAC8E5 /* ImageCaching.swift in Sources */,
0CA4ECC026E685C900BAC8E5 /* ImageProcessors+Anonymous.swift in Sources */,
0CA4ECC826E6864D00BAC8E5 /* ImageProcessors+RoundedCorners.swift in Sources */,
0CB402DB25B656D200F5A241 /* TaskFetchDecodedImage.swift in Sources */,
0CB402DB25B656D200F5A241 /* TaskFetchOriginalImage.swift in Sources */,
0C472F842654AD88007FC0F0 /* ImageRequestKeys.swift in Sources */,
0CE6202126542F7200AAB8C3 /* DataPublisher.swift in Sources */,
0CB0479A2856D9AC00DF9B6D /* Cache.swift in Sources */,
Expand All @@ -1759,7 +1759,7 @@
0CA4ECBE26E685A900BAC8E5 /* ImageProcessors+Circle.swift in Sources */,
0CE2D9BA2084FDDD00934B28 /* ImageDecoding.swift in Sources */,
0CC36A1925B8BC2500811018 /* RateLimiter.swift in Sources */,
0CB402D525B6569700F5A241 /* TaskFetchOriginalImageData.swift in Sources */,
0CB402D525B6569700F5A241 /* TaskFetchOriginalData.swift in Sources */,
0CA4ECAD26E683E300BAC8E5 /* ImageEncoders.swift in Sources */,
0CA4ECC626E6862A00BAC8E5 /* ImageProcessors+CoreImage.swift in Sources */,
0C2CD6EB25B67FB30017018F /* ImagePipelineTask.swift in Sources */,
Expand Down
4 changes: 2 additions & 2 deletions Sources/Nuke/Caching/ImageCaching.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ public struct ImageCacheKey: Hashable, Sendable {
// This is faster than using AnyHashable (and it shows in performance tests).
enum Inner: Hashable, Sendable {
case custom(String)
case `default`(CacheKey)
case `default`(MemoryCacheKey)
}

public init(key: String) {
self.key = .custom(key)
}

public init(request: ImageRequest) {
self.key = .default(request.makeImageCacheKey())
self.key = .default(MemoryCacheKey(request))
}
}
62 changes: 16 additions & 46 deletions Sources/Nuke/Internal/ImageRequestKeys.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,8 @@

import Foundation

extension ImageRequest {

// MARK: - Cache Keys

/// A key for processed image in memory cache.
func makeImageCacheKey() -> CacheKey {
CacheKey(self)
}

/// A key for processed image data in disk cache.
func makeDataCacheKey() -> String {
"\(preferredImageId)\(thumbnail?.identifier ?? "")\(ImageProcessors.Composition(processors).identifier)"
}

// MARK: - Load Keys

/// A key for deduplicating operations for fetching the processed image.
func makeImageLoadKey() -> ImageLoadKey {
ImageLoadKey(self)
}

/// A key for deduplicating operations for fetching the decoded image.
func makeDecodedImageLoadKey() -> DecodedImageLoadKey {
DecodedImageLoadKey(self)
}

/// A key for deduplicating operations for fetching the original image.
func makeDataLoadKey() -> DataLoadKey {
DataLoadKey(self)
}
}

/// Uniquely identifies a cache processed image.
final class CacheKey: Hashable, Sendable {
final class MemoryCacheKey: Hashable, Sendable {
// Using a reference type turned out to be significantly faster
private let imageId: String?
private let scale: Float
Expand All @@ -58,21 +26,23 @@ final class CacheKey: Hashable, Sendable {
hasher.combine(processors.count)
}

static func == (lhs: CacheKey, rhs: CacheKey) -> Bool {
static func == (lhs: MemoryCacheKey, rhs: MemoryCacheKey) -> Bool {
lhs.imageId == rhs.imageId && lhs.scale == rhs.scale && lhs.thumbnail == rhs.thumbnail && lhs.processors == rhs.processors
}
}

// MARK: - Identifying Tasks

/// Uniquely identifies a task of retrieving the processed image.
final class ImageLoadKey: Hashable, Sendable {
let cacheKey: CacheKey
final class TaskLoadImageKey: Hashable, Sendable {
let cacheKey: MemoryCacheKey
let options: ImageRequest.Options
let loadKey: DataLoadKey
let loadKey: TaskFetchOriginalDataKey

init(_ request: ImageRequest) {
self.cacheKey = CacheKey(request)
self.cacheKey = MemoryCacheKey(request)
self.options = request.options
self.loadKey = DataLoadKey(request)
self.loadKey = TaskFetchOriginalDataKey(request)
}

func hash(into hasher: inout Hasher) {
Expand All @@ -81,26 +51,26 @@ final class ImageLoadKey: Hashable, Sendable {
hasher.combine(loadKey.hashValue)
}

static func == (lhs: ImageLoadKey, rhs: ImageLoadKey) -> Bool {
static func == (lhs: TaskLoadImageKey, rhs: TaskLoadImageKey) -> Bool {
lhs.cacheKey == rhs.cacheKey && lhs.options == rhs.options && lhs.loadKey == rhs.loadKey
}
}

/// Uniquely identifies a task of retrieving the decoded image.
struct DecodedImageLoadKey: Hashable {
let dataLoadKey: DataLoadKey
/// Uniquely identifies a task of retrieving the original image.
struct TaskFetchOriginalImageKey: Hashable {
let dataLoadKey: TaskFetchOriginalDataKey
let scale: Float
let thumbnail: ImageRequest.ThumbnailOptions?

init(_ request: ImageRequest) {
self.dataLoadKey = DataLoadKey(request)
self.dataLoadKey = TaskFetchOriginalDataKey(request)
self.scale = request.scale ?? 1
self.thumbnail = request.thumbnail
}
}

/// Uniquely identifies a task of retrieving the original image dataa.
struct DataLoadKey: Hashable {
/// Uniquely identifies a task of retrieving the original image data.
struct TaskFetchOriginalDataKey: Hashable {
private let imageId: String?
private let cachePolicy: URLRequest.CachePolicy
private let allowsCellularAccess: Bool
Expand Down
28 changes: 14 additions & 14 deletions Sources/Nuke/Pipeline/ImagePipeline.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ public final class ImagePipeline: @unchecked Sendable {

private var tasks = [ImageTask: TaskSubscription]()

private let tasksLoadData: TaskPool<ImageLoadKey, (Data, URLResponse?), Error>
private let tasksLoadImage: TaskPool<ImageLoadKey, ImageResponse, Error>
private let tasksFetchDecodedImage: TaskPool<DecodedImageLoadKey, ImageResponse, Error>
private let tasksFetchOriginalImageData: TaskPool<DataLoadKey, (Data, URLResponse?), Error>
private let tasksLoadData: TaskPool<TaskLoadImageKey, (Data, URLResponse?), Error>
private let tasksLoadImage: TaskPool<TaskLoadImageKey, ImageResponse, Error>
private let tasksFetchDecodedImage: TaskPool<TaskFetchOriginalImageKey, ImageResponse, Error>
private let tasksFetchOriginalImageData: TaskPool<TaskFetchOriginalDataKey, (Data, URLResponse?), Error>

// The queue on which the entire subsystem is synchronized.
let queue = DispatchQueue(label: "com.github.kean.Nuke.ImagePipeline", qos: .userInitiated)
Expand Down Expand Up @@ -501,11 +501,11 @@ public final class ImagePipeline: @unchecked Sendable {
//
// `loadImage()` call is represented by TaskLoadImage:
//
// TaskLoadImage -> TaskFetchDecodedImage -> TaskFetchOriginalImageData
// TaskLoadImage -> TaskFetchOriginalImage -> TaskFetchOriginalData
//
// `loadData()` call is represented by TaskLoadData:
//
// TaskLoadData -> TaskFetchOriginalImageData
// TaskLoadData -> TaskFetchOriginalData
//
//
// Each task represents a resource or a piece of work required to produce the
Expand All @@ -515,27 +515,27 @@ public final class ImagePipeline: @unchecked Sendable {
// is created. The work is split between tasks to minimize any duplicated work.

func makeTaskLoadImage(for request: ImageRequest) -> AsyncTask<ImageResponse, Error>.Publisher {
tasksLoadImage.publisherForKey(request.makeImageLoadKey()) {
tasksLoadImage.publisherForKey(TaskLoadImageKey(request)) {
TaskLoadImage(self, request)
}
}

func makeTaskLoadData(for request: ImageRequest) -> AsyncTask<(Data, URLResponse?), Error>.Publisher {
tasksLoadData.publisherForKey(request.makeImageLoadKey()) {
tasksLoadData.publisherForKey(TaskLoadImageKey(request)) {
TaskLoadData(self, request)
}
}

func makeTaskFetchDecodedImage(for request: ImageRequest) -> AsyncTask<ImageResponse, Error>.Publisher {
tasksFetchDecodedImage.publisherForKey(request.makeDecodedImageLoadKey()) {
TaskFetchDecodedImage(self, request)
func makeTaskFetchOriginalImage(for request: ImageRequest) -> AsyncTask<ImageResponse, Error>.Publisher {
tasksFetchDecodedImage.publisherForKey(TaskFetchOriginalImageKey(request)) {
TaskFetchOriginalImage(self, request)
}
}

func makeTaskFetchOriginalImageData(for request: ImageRequest) -> AsyncTask<(Data, URLResponse?), Error>.Publisher {
tasksFetchOriginalImageData.publisherForKey(request.makeDataLoadKey()) {
func makeTaskFetchOriginalData(for request: ImageRequest) -> AsyncTask<(Data, URLResponse?), Error>.Publisher {
tasksFetchOriginalImageData.publisherForKey(TaskFetchOriginalDataKey(request)) {
request.publisher == nil ?
TaskFetchOriginalImageData(self, request) :
TaskFetchOriginalData(self, request) :
TaskFetchWithPublisher(self, request)
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Nuke/Pipeline/ImagePipelineCache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ extension ImagePipeline.Cache {
if let customKey = pipeline.delegate.cacheKey(for: request, pipeline: pipeline) {
return customKey
}
return request.makeDataCacheKey() // Use the default key
return "\(request.preferredImageId)\(request.thumbnail?.identifier ?? "")\(ImageProcessors.Composition(request.processors).identifier)"
}

// MARK: Misc
Expand Down
10 changes: 5 additions & 5 deletions Sources/Nuke/Prefetching/ImagePrefetcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public final class ImagePrefetcher: @unchecked Sendable {
public var didComplete: (() -> Void)?

private let pipeline: ImagePipeline
private var tasks = [ImageLoadKey: Task]()
private var tasks = [TaskLoadImageKey: Task]()
private let destination: Destination
private var _priority: ImageRequest.Priority = .low
let queue = OperationQueue() // internal for testing
Expand Down Expand Up @@ -122,7 +122,7 @@ public final class ImagePrefetcher: @unchecked Sendable {
guard pipeline.cache[request] == nil else {
return
}
let key = request.makeImageLoadKey()
let key = TaskLoadImageKey(request)
guard tasks[key] == nil else {
return
}
Expand Down Expand Up @@ -189,7 +189,7 @@ public final class ImagePrefetcher: @unchecked Sendable {
}

private func _stopPrefetching(with request: ImageRequest) {
if let task = tasks.removeValue(forKey: request.makeImageLoadKey()) {
if let task = tasks.removeValue(forKey: TaskLoadImageKey(request)) {
task.cancel()
}
}
Expand All @@ -211,13 +211,13 @@ public final class ImagePrefetcher: @unchecked Sendable {
}

private final class Task: @unchecked Sendable {
let key: ImageLoadKey
let key: TaskLoadImageKey
let request: ImageRequest
weak var imageTask: ImageTask?
weak var operation: Operation?
var onCancelled: (() -> Void)?

init(request: ImageRequest, key: ImageLoadKey) {
init(request: ImageRequest, key: TaskLoadImageKey) {
self.request = request
self.key = key
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Foundation

/// Fetches original image from the data loader (`DataLoading`) and stores it
/// in the disk cache (`DataCaching`).
final class TaskFetchOriginalImageData: ImagePipelineTask<(Data, URLResponse?)> {
final class TaskFetchOriginalData: ImagePipelineTask<(Data, URLResponse?)> {
private var urlResponse: URLResponse?
private var resumableData: ResumableData?
private var resumedDataCount: Int64 = 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@
import Foundation

/// Receives data from ``TaskLoadImageData`` and decodes it as it arrives.
final class TaskFetchDecodedImage: ImagePipelineTask<ImageResponse> {
final class TaskFetchOriginalImage: ImagePipelineTask<ImageResponse> {
private var decoder: (any ImageDecoding)?

override func start() {
dependency = pipeline.makeTaskFetchOriginalImageData(for: request).subscribe(self) { [weak self] in
dependency = pipeline.makeTaskFetchOriginalData(for: request).subscribe(self) { [weak self] in
self?.didReceiveData($0.0, urlResponse: $0.1, isCompleted: $1)
}
}

/// Receiving data from `OriginalDataTask`.
/// Receiving data from `TaskFetchOriginalData`.
private func didReceiveData(_ data: Data, urlResponse: URLResponse?, isCompleted: Bool) {
guard isCompleted || pipeline.configuration.isProgressiveDecodingEnabled else {
return
Expand Down
2 changes: 1 addition & 1 deletion Sources/Nuke/Tasks/TaskLoadData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ final class TaskLoadData: ImagePipelineTask<(Data, URLResponse?)> {
}

let request = self.request.withProcessors([])
dependency = pipeline.makeTaskFetchOriginalImageData(for: request).subscribe(self) { [weak self] in
dependency = pipeline.makeTaskFetchOriginalData(for: request).subscribe(self) { [weak self] in
self?.didReceiveData($0.0, urlResponse: $0.1, isCompleted: $1)
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Nuke/Tasks/TaskLoadImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ final class TaskLoadImage: ImagePipelineTask<ImageResponse> {
self?.process($0, isCompleted: $1, processor: processor)
}
} else {
dependency = pipeline.makeTaskFetchDecodedImage(for: request).subscribe(self) { [weak self] in
dependency = pipeline.makeTaskFetchOriginalImage(for: request).subscribe(self) { [weak self] in
self?.didReceiveResponse($0, isCompleted: $1)
}
}
Expand Down
Loading

0 comments on commit 1f1fc21

Please sign in to comment.