Skip to content

Commit

Permalink
Merge pull request #777 from kean/fix-redundant-decompression
Browse files Browse the repository at this point in the history
Nuke 12.6
  • Loading branch information
kean authored Apr 21, 2024
2 parents 0232456 + dbb4e99 commit 6b95584
Show file tree
Hide file tree
Showing 37 changed files with 506 additions and 496 deletions.
54 changes: 27 additions & 27 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,22 @@ on:

jobs:
ios-latest:
name: Unit Tests (iOS 16.4, Xcode 14.3.1)
runs-on: macOS-13
name: Unit Tests (iOS 17.4, Xcode 15.3)
runs-on: macOS-14
env:
DEVELOPER_DIR: /Applications/Xcode_14.3.1.app/Contents/Developer
DEVELOPER_DIR: /Applications/Xcode_15.3.app/Contents/Developer
steps:
- uses: actions/checkout@v2
- name: Run Tests
run: |
Scripts/test.sh -s "Nuke" -d "OS=16.4,name=iPhone 14 Pro"
Scripts/test.sh -s "NukeUI" -d "OS=16.4,name=iPhone 14 Pro"
Scripts/test.sh -s "NukeExtensions" -d "OS=16.4,name=iPhone 14 Pro"
Scripts/test.sh -s "Nuke" -d "OS=17.4,name=iPhone 15 Pro"
Scripts/test.sh -s "NukeUI" -d "OS=17.4,name=iPhone 15 Pro"
Scripts/test.sh -s "NukeExtensions" -d "OS=17.4,name=iPhone 15 Pro"
macos-latest:
name: Unit Tests (macOS, Xcode 14.3.1)
runs-on: macOS-13
env:
DEVELOPER_DIR: /Applications/Xcode_14.3.1.app/Contents/Developer
name: Unit Tests (macOS, Xcode 15.3)
runs-on: macOS-14
env:
DEVELOPER_DIR: /Applications/Xcode_15.3.app/Contents/Developer
steps:
- uses: actions/checkout@v2
- name: Run Tests
Expand All @@ -34,17 +34,17 @@ jobs:
Scripts/test.sh -s "NukeUI" -d "platform=macOS"
Scripts/test.sh -s "NukeExtensions" -d "platform=macOS"
tvos-latest:
name: Unit Tests (tvOS 16.4, Xcode 14.3.1)
runs-on: macOS-13
env:
DEVELOPER_DIR: /Applications/Xcode_14.3.1.app/Contents/Developer
name: Unit Tests (tvOS 17.4, Xcode 15.3)
runs-on: macOS-14
env:
DEVELOPER_DIR: /Applications/Xcode_15.3.app/Contents/Developer
steps:
- uses: actions/checkout@v2
- name: Run Tests
run: |
Scripts/test.sh -s "Nuke" -d "OS=16.4,name=Apple TV"
Scripts/test.sh -s "NukeUI" -d "OS=16.4,name=Apple TV"
Scripts/test.sh -s "NukeExtensions" -d "OS=16.4,name=Apple TV"
Scripts/test.sh -s "Nuke" -d "OS=17.4,name=Apple TV"
Scripts/test.sh -s "NukeUI" -d "OS=17.4,name=Apple TV"
Scripts/test.sh -s "NukeExtensions" -d "OS=17.4,name=Apple TV"
# There is a problem with watchOS runners where they often fail to launch on CI
#
# watchos-latest:
Expand All @@ -59,27 +59,27 @@ jobs:
# Scripts/test.sh -s "Nuke" -d "OS=9.1,name=Apple Watch Series 8 (45mm)"
# Scripts/test.sh -s "NukeUI" -d "OS=9.1,name=Apple Watch Series 8 (45mm)"
# Scripts/test.sh -s "Nuke Extensions" -d "OS=9.1,name=Apple Watch Series 8 (45mm)"
ios-xcode-14-1:
name: Unit Tests (iOS 16.1, Xcode 14.1)
ios-xcode-14-3-1:
name: Unit Tests (iOS 16.4, Xcode 14.3.1)
runs-on: macOS-13
env:
DEVELOPER_DIR: /Applications/Xcode_14.1.app/Contents/Developer
DEVELOPER_DIR: /Applications/Xcode_14.3.1.app/Contents/Developer
steps:
- uses: actions/checkout@v2
- name: Run Tests
run: |
Scripts/test.sh -s "Nuke" -d "OS=16.1,name=iPhone 14 Pro"
Scripts/test.sh -s "NukeUI" -d "OS=16.1,name=iPhone 14 Pro"
Scripts/test.sh -s "NukeExtensions" -d "OS=16.1,name=iPhone 14 Pro"
Scripts/test.sh -s "Nuke" -d "OS=16.4,name=iPhone 14 Pro"
Scripts/test.sh -s "NukeUI" -d "OS=16.4,name=iPhone 14 Pro"
Scripts/test.sh -s "NukeExtensions" -d "OS=16.4,name=iPhone 14 Pro"
ios-thread-safety:
name: Thread Safety Tests (TSan Enabled)
runs-on: macOS-13
env:
DEVELOPER_DIR: /Applications/Xcode_14.3.1.app/Contents/Developer
runs-on: macOS-14
env:
DEVELOPER_DIR: /Applications/Xcode_15.3.app/Contents/Developer
steps:
- uses: actions/checkout@v2
- name: Run Tests
run: Scripts/test.sh -s "Nuke Thread Safety Tests" -d "OS=16.4,name=iPhone 14 Pro"
run: Scripts/test.sh -s "Nuke Thread Safety Tests" -d "OS=17.4,name=iPhone 15 Pro"
# ios-memory-management-tests:
# name: Memory Management Tests
# runs-on: macOS-13
Expand Down
20 changes: 8 additions & 12 deletions Nuke.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,6 @@
0C8D7BED1D9DC02B00D12EB7 /* Nuke.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0C9174901BAE99EE004A7905 /* Nuke.framework */; };
0C8D7BF51D9DC07E00D12EB7 /* DataCachePeformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C8D74201D9D6EEB0036349E /* DataCachePeformanceTests.swift */; };
0C8DC723209B842600084AA6 /* cat.gif in Resources */ = {isa = PBXBuildFile; fileRef = 0C8DC722209B842600084AA6 /* cat.gif */; };
0C9165E626431942006B1D4F /* OperationTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C9165E526431942006B1D4F /* OperationTask.swift */; };
0C91B0EC2438E287007F9100 /* ResizeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C91B0EB2438E287007F9100 /* ResizeTests.swift */; };
0C91B0EE2438E307007F9100 /* CircleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C91B0ED2438E307007F9100 /* CircleTests.swift */; };
0C91B0F02438E352007F9100 /* RoundedCornersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C91B0EF2438E352007F9100 /* RoundedCornersTests.swift */; };
Expand Down Expand Up @@ -200,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 @@ -447,7 +446,6 @@
0C8D7BDE1D9DBF1600D12EB7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
0C8D7BE81D9DC02B00D12EB7 /* Nuke Performance Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Nuke Performance Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
0C8DC722209B842600084AA6 /* cat.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = cat.gif; sourceTree = "<group>"; };
0C9165E526431942006B1D4F /* OperationTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationTask.swift; sourceTree = "<group>"; };
0C9174901BAE99EE004A7905 /* Nuke.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Nuke.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0C91B0EB2438E287007F9100 /* ResizeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResizeTests.swift; sourceTree = "<group>"; };
0C91B0ED2438E307007F9100 /* CircleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -495,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 @@ -971,10 +969,9 @@
0C2CD6EA25B67FB30017018F /* ImagePipelineTask.swift */,
0CB4030025B6639200F5A241 /* TaskLoadImage.swift */,
0C2A368A26437BF100F1D000 /* TaskLoadData.swift */,
0CB402DA25B656D200F5A241 /* TaskFetchDecodedImage.swift */,
0CB402D425B6569700F5A241 /* TaskFetchOriginalImageData.swift */,
0CB402DA25B656D200F5A241 /* TaskFetchOriginalImage.swift */,
0CB402D425B6569700F5A241 /* TaskFetchOriginalData.swift */,
0CE6202226543B6A00AAB8C3 /* TaskFetchWithPublisher.swift */,
0C9165E526431942006B1D4F /* OperationTask.swift */,
);
path = Tasks;
sourceTree = "<group>";
Expand Down Expand Up @@ -1731,12 +1728,11 @@
0CA4ECD026E68FC000BAC8E5 /* DataCaching.swift in Sources */,
0CA4ECCA26E6868300BAC8E5 /* ImageProcessingOptions.swift in Sources */,
0C53C8B1263C968200E62D03 /* ImagePipelineDelegate.swift in Sources */,
0C9165E626431942006B1D4F /* OperationTask.swift in Sources */,
0CA4ECBC26E6856300BAC8E5 /* ImageDecompression.swift in Sources */,
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 @@ -1763,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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ Nuke supports [Swift Package Manager](https://www.swift.org/package-manager/), w

## Documentation

Nuke is easy to learn and use thanks to an extensive documentation. The best place to start is the [**Getting Started**](https://kean-docs.github.io/nuke/documentation/nuke/getting-started/) guide.
Nuke is easy to learn and use, thanks to its extensive documentation. The [**Getting Started**](https://kean-docs.github.io/nuke/documentation/nuke/getting-started/) guide is the best place to start learning how to use it.

Nuke package ships with four modules that you can install depending on your needs:
The package ships with four modules that you can install depending on your needs:

|Module|Description|
|--|--|
Expand All @@ -45,7 +45,7 @@ Nuke package ships with four modules that you can install depending on your need
|[**NukeExtensions**](https://kean-docs.github.io/nukeextensions/documentation/nukeextensions/)|The extensions for `UIImageView` (UIKit, AppKit)|
|[**NukeVideo**](https://kean-docs.github.io/nukevideo/documentation/nukevideo/)|The components for decoding and playing short videos|

To see more usage examples, check out [**Nuke Demo**](https://github.com/kean/NukeDemo).
Check out [**Nuke Demo**](https://github.com/kean/NukeDemo) for more usage examples.

> Upgrading from the previous version? Use a [**Migration Guide**](https://github.com/kean/Nuke/tree/master/Documentation/Migrations).
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))
}
}
14 changes: 8 additions & 6 deletions Sources/Nuke/Encoding/ImageEncoders+ImageIO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,19 +47,21 @@ extension ImageEncoders {
}

public func encode(_ image: PlatformImage) -> Data? {
let data = NSMutableData()
guard let source = image.cgImage,
let data = CFDataCreateMutable(nil, 0),
let destination = CGImageDestinationCreateWithData(data, type.rawValue as CFString, 1, nil) else {
return nil
}
var options: [CFString: Any] = [
kCGImageDestinationLossyCompressionQuality: compressionRatio
]
#if canImport(UIKit)
options[kCGImagePropertyOrientation] = CGImagePropertyOrientation(image.imageOrientation).rawValue
#endif
guard let source = image.cgImage,
let destination = CGImageDestinationCreateWithData(data as CFMutableData, type.rawValue as CFString, 1, nil) else {
return nil
}
CGImageDestinationAddImage(destination, source, options as CFDictionary)
CGImageDestinationFinalize(destination)
guard CGImageDestinationFinalize(destination) else {
return nil
}
return data as Data
}
}
Expand Down
89 changes: 27 additions & 62 deletions Sources/Nuke/Internal/ImageRequestKeys.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,98 +4,73 @@

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
private let thumbnail: ImageRequest.ThumbnailOptions?
private let processors: [any ImageProcessing]

init(_ request: ImageRequest) {
self.imageId = request.preferredImageId
self.scale = request.scale ?? 1
self.thumbnail = request.thumbnail
self.processors = request.processors
}

func hash(into hasher: inout Hasher) {
hasher.combine(imageId)
hasher.combine(scale)
hasher.combine(thumbnail)
hasher.combine(processors.count)
}

static func == (lhs: CacheKey, rhs: CacheKey) -> Bool {
lhs.imageId == rhs.imageId && lhs.thumbnail == rhs.thumbnail && lhs.processors == rhs.processors
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
let options: ImageRequest.Options
let loadKey: DataLoadKey
final class TaskLoadImageKey: Hashable, Sendable {
private let loadKey: TaskFetchOriginalImageKey
private let options: ImageRequest.Options
private let processors: [any ImageProcessing]

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

func hash(into hasher: inout Hasher) {
hasher.combine(cacheKey.hashValue)
hasher.combine(options.hashValue)
hasher.combine(loadKey.hashValue)
hasher.combine(options.hashValue)
hasher.combine(processors.count)
}

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

/// Uniquely identifies a task of retrieving the decoded image.
struct DecodedImageLoadKey: Hashable {
let dataLoadKey: DataLoadKey
let thumbnail: ImageRequest.ThumbnailOptions?
/// Uniquely identifies a task of retrieving the original image.
struct TaskFetchOriginalImageKey: Hashable {
private let dataLoadKey: TaskFetchOriginalDataKey
private let scale: Float
private 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 All @@ -112,13 +87,3 @@ struct DataLoadKey: Hashable {
}
}
}

struct ImageProcessingKey: Equatable, Hashable {
let imageId: ObjectIdentifier
let processorId: AnyHashable

init(image: ImageResponse, processor: any ImageProcessing) {
self.imageId = ObjectIdentifier(image.image)
self.processorId = processor.hashableIdentifier
}
}
Loading

0 comments on commit 6b95584

Please sign in to comment.