Skip to content

Commit 884d7c4

Browse files
Resolve complete strict concurrency issues (#5)
* Begin concurrency process. * Make it concurrent. * Add conveniences. * Add defaults. * Disable strict concurrency for now. * Point to etcetera 3
1 parent dfccb28 commit 884d7c4

14 files changed

+218
-100
lines changed

Package.resolved

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
"package": "Etcetera",
66
"repositoryURL": "https://github.com/jaredsinclair/etcetera",
77
"state": {
8-
"branch": "master",
9-
"revision": "a2ea700f45a94ad6055eca2381eb3e65b8bc1018",
10-
"version": null
8+
"branch": null,
9+
"revision": "3dc4efd2d72bc6bb857c0d7d801e913935ce325c",
10+
"version": "3.0.0"
1111
}
1212
}
1313
]

Package.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,24 @@ import PackageDescription
55
let package = Package(
66
name: "ImageCache",
77
platforms: [
8-
.iOS(.v13), .tvOS(.v13)
8+
.iOS("17"), .tvOS("17")
99
],
1010
products: [
1111
.library(name: "ImageCache", targets: ["ImageCache"])
1212
],
1313
dependencies: [
14-
.package(url: "https://github.com/jaredsinclair/etcetera", .branch("master"))
14+
.package(url: "https://github.com/jaredsinclair/etcetera", .upToNextMajor(from: "3.0.0"))
1515
],
1616
targets: [
1717
.target(
1818
name: "ImageCache",
1919
dependencies: [
2020
"Etcetera"
21-
]),
21+
]
22+
// Uncomment to enable complete strict concurrency checking. In a
23+
// future update, it would be handy if this were scriptable in CI:
24+
// swiftSettings: [ .unsafeFlags(["-Xfrontend", "-strict-concurrency=complete"]) ]
25+
),
2226
.testTarget(name: "ImageCacheTests",
2327
dependencies: [
2428
"ImageCache"

Sources/ImageCache/DeferredValue.swift

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@
66
// Copyright © 2020 Nice Boy LLC. All rights reserved.
77
//
88

9-
final class DeferredValue<T> {
10-
var value: T?
9+
import Etcetera
10+
11+
final class DeferredValue<T>: Sendable {
12+
13+
var value: T? {
14+
get { _value.current }
15+
set { _value.current = newValue }
16+
}
17+
18+
private let _value = Protected<T?>(nil)
19+
1120
}

Sources/ImageCache/DownloadResult.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import Foundation
1111
/// Communicates to `ImageCache` whether the result of a download operation was
1212
/// that a new file was freshly downloaded, or whether a previously-downloaded
1313
/// file was able to be used.
14-
enum DownloadResult {
14+
enum DownloadResult: Sendable {
1515

1616
/// A fresh file was downloaded and is available locally at a file URL.
1717
case fresh(URL)

Sources/ImageCache/Format.swift

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import Etcetera
1717
extension ImageCache {
1818

1919
/// Describes the format options to be used when processing a source image.
20-
public enum Format {
20+
public enum Format: Sendable {
2121

2222
// MARK: Typealiases
2323

@@ -64,7 +64,15 @@ extension ImageCache {
6464
/// - parameter contentScale: The number of pixels per point, which is
6565
/// used to reckon the output image size relative to the requested
6666
/// `size`. Pass `0` to use the native defaults for the current device.
67-
case scaled(size: CGSize, mode: ContentMode, bleed: CGFloat, opaque: Bool, cornerRadius: CGFloat, border: Border?, contentScale: ContentScale)
67+
case scaled(
68+
size: CGSize,
69+
mode: ContentMode = .scaleAspectFill,
70+
bleed: CGFloat = 0,
71+
opaque: Bool = false,
72+
cornerRadius: CGFloat = 0,
73+
border: Border? = nil,
74+
contentScale: ContentScale = 0
75+
)
6876

6977
/// Scale the source image and crop it to an elliptical shape. The
7078
/// resulting image will have transparent contents in the corners.
@@ -77,7 +85,11 @@ extension ImageCache {
7785
/// - parameter contentScale: The number of pixels per point, which is
7886
/// used to reckon the output image size relative to the requested
7987
/// `size`. Pass `0` to use the native defaults for the current device.
80-
case round(size: CGSize, border: Border?, contentScale: ContentScale)
88+
case round(
89+
size: CGSize,
90+
border: Border? = nil,
91+
contentScale: ContentScale = 0
92+
)
8193

8294
/// Draw the source image using a developer-supplied formatting block.
8395
///
@@ -92,7 +104,7 @@ extension ImageCache {
92104
/// image. The developer does not need to cache the returned image.
93105
/// ImageCache will cache the result in the same manner as images drawn
94106
/// using the other formats.
95-
case custom(editKey: String, block: (ImageCache.Image) -> ImageCache.Image)
107+
case custom(editKey: String, block: @Sendable (ImageCache.Image) -> ImageCache.Image)
96108

97109
}
98110

@@ -105,7 +117,7 @@ extension ImageCache {
105117
extension ImageCache.Format {
106118

107119
/// Platform-agnostic analogue to UIView.ContentMode
108-
public enum ContentMode {
120+
public enum ContentMode: Sendable {
109121

110122
/// Contents scaled to fill with fixed aspect ratio. Some portion of
111123
/// the content may be clipped.
@@ -126,7 +138,7 @@ extension ImageCache.Format {
126138
extension ImageCache.Format {
127139

128140
/// Border styles you can use when drawing a scaled or round image format.
129-
public enum Border: Hashable {
141+
public enum Border: Hashable, Sendable {
130142

131143
case hairline(ImageCache.Color)
132144

Sources/ImageCache/GCD.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88

99
import Foundation
1010

11-
func onMain(_ block: @escaping () -> Void) {
11+
func onMain(_ block: @escaping @Sendable @MainActor () -> Void) {
1212
DispatchQueue.main.async { block() }
1313
}
1414

15-
func deferred(on queue: OperationQueue, block: @escaping () -> Void) {
15+
func deferred(on queue: OperationQueue, block: @escaping @Sendable () -> Void) {
1616
onMain { queue.addOperation(block) }
1717
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import Foundation
2+
3+
extension ImageCache {
4+
5+
/// Equivalent to `image(from:format:completion:)` but as `async`.
6+
public func image(
7+
from url: URL,
8+
format: Format = .original
9+
) async -> Image? {
10+
await image(from: .url(url), format: format)
11+
}
12+
13+
/// Equivalent to `image(from:format:completion:)` but as `async`.
14+
public func image(
15+
from source: OriginalImageSource,
16+
format: Format = .original
17+
) async -> Image? {
18+
if let cached = memoryCachedImage(from: source, format: format) {
19+
return cached
20+
}
21+
return await withCheckedContinuation { (continuation: CheckedContinuation<Image?, Never>) in
22+
self.image(from: source, format: format, completion: { image in
23+
continuation.resume(returning: image)
24+
})
25+
}
26+
}
27+
28+
}

0 commit comments

Comments
 (0)