Skip to content

Commit

Permalink
Fix more concurrency warnings
Browse files Browse the repository at this point in the history
  • Loading branch information
kean committed May 3, 2024
1 parent 72fd1dd commit 8bc6c59
Show file tree
Hide file tree
Showing 17 changed files with 110 additions and 33 deletions.
2 changes: 1 addition & 1 deletion Nuke.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -980,8 +980,8 @@
0C0FD5D31CA47FE1002A78FB /* ImagePipeline.swift */,
0CF1754B22913F9800A8946E /* ImagePipeline+Configuration.swift */,
0C53C8B0263C968200E62D03 /* ImagePipeline+Delegate.swift */,
0CBA07852852DA8B00CE29F4 /* ImagePipeline+Error.swift */,
0C78A2A6263F4E680051E0FF /* ImagePipeline+Cache.swift */,
0CBA07852852DA8B00CE29F4 /* ImagePipeline+Error.swift */,
);
path = Pipeline;
sourceTree = "<group>";
Expand Down
6 changes: 4 additions & 2 deletions Sources/Nuke/Caching/Cache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ final class Cache<Key: Hashable, Value>: @unchecked Sendable {
self.memoryPressure.resume()

#if os(iOS) || os(tvOS) || os(visionOS)
registerForEnterBackground()
Task {
await registerForEnterBackground()
}
#endif
}

Expand All @@ -68,7 +70,7 @@ final class Cache<Key: Hashable, Value>: @unchecked Sendable {
}

#if os(iOS) || os(tvOS) || os(visionOS)
private func registerForEnterBackground() {
@MainActor private func registerForEnterBackground() {
notificationObserver = NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil) { [weak self] _ in
self?.clearCacheOnEnterBackground()
}
Expand Down
1 change: 1 addition & 0 deletions Sources/Nuke/ImageResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import UIKit
#if canImport(AppKit)
import AppKit
#endif

/// An image response that contains a fetched image and some metadata.
public struct ImageResponse: @unchecked Sendable {
/// An image container with an image and associated metadata.
Expand Down
2 changes: 1 addition & 1 deletion Sources/Nuke/Internal/Graphics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ extension CGSize {
enum Screen {
#if os(iOS) || os(tvOS)
/// Returns the current screen scale.
static let scale: CGFloat = UIScreen.main.scale
static let scale: CGFloat = UITraitCollection.current.displayScale
#elseif os(watchOS)
/// Returns the current screen scale.
static let scale: CGFloat = WKInterfaceDevice.current().screenScale
Expand Down
4 changes: 3 additions & 1 deletion Sources/Nuke/Internal/Log.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,23 @@ import os
func signpost(_ object: AnyObject, _ name: StaticString, _ type: OSSignpostType, _ message: @autoclosure () -> String) {
guard ImagePipeline.Configuration.isSignpostLoggingEnabled else { return }

let log = log.value
let signpostId = OSSignpostID(log: log, object: object)
os_signpost(type, log: log, name: name, signpostID: signpostId, "%{public}s", message())
}

func signpost<T>(_ name: StaticString, _ work: () throws -> T) rethrows -> T {
guard ImagePipeline.Configuration.isSignpostLoggingEnabled else { return try work() }

let log = log.value
let signpostId = OSSignpostID(log: log)
os_signpost(.begin, log: log, name: name, signpostID: signpostId)
let result = try work()
os_signpost(.end, log: log, name: name, signpostID: signpostId)
return result
}

private let log = OSLog(subsystem: "com.github.kean.Nuke.ImagePipeline", category: "Image Loading")
private let log = Atomic(value: OSLog(subsystem: "com.github.kean.Nuke.ImagePipeline", category: "Image Loading"))

private let byteFormatter = ByteCountFormatter()

Expand Down
2 changes: 1 addition & 1 deletion Sources/Nuke/Loading/DataLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ private final class _DataLoader: NSObject, URLSessionDataDelegate {

// MARK: Internal

private final class _Handler {
private final class _Handler: @unchecked Sendable {
let didReceiveData: (Data, URLResponse) -> Void
let completion: (Error?) -> Void

Expand Down
7 changes: 6 additions & 1 deletion Sources/Nuke/Pipeline/ImagePipeline+Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,12 @@ extension ImagePipeline {
/// metrics in `os_signpost` Instrument. For more information see
/// https://developer.apple.com/documentation/os/logging and
/// https://developer.apple.com/videos/play/wwdc2018/405/.
public static var isSignpostLoggingEnabled = false
public static var isSignpostLoggingEnabled: Bool {
get { _isSignpostLoggingEnabled.value }
set { _isSignpostLoggingEnabled.value = newValue }
}

private static let _isSignpostLoggingEnabled = Atomic(value: false)

private var isCustomImageCacheProvided = false

Expand Down
6 changes: 3 additions & 3 deletions Sources/Nuke/Processing/ImageDecompression.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ enum ImageDecompression {

// MARK: Managing Decompression State

static var isDecompressionNeededAK: UInt8 = 0
static let isDecompressionNeededAK = malloc(1)!

static func setDecompressionNeeded(_ isDecompressionNeeded: Bool, for image: PlatformImage) {
objc_setAssociatedObject(image, &isDecompressionNeededAK, isDecompressionNeeded, .OBJC_ASSOCIATION_RETAIN)
objc_setAssociatedObject(image, isDecompressionNeededAK, isDecompressionNeeded, .OBJC_ASSOCIATION_RETAIN)
}

static func isDecompressionNeeded(for image: PlatformImage) -> Bool? {
objc_getAssociatedObject(image, &isDecompressionNeededAK) as? Bool
objc_getAssociatedObject(image, isDecompressionNeededAK) as? Bool
}
}
6 changes: 3 additions & 3 deletions Sources/NukeExtensions/ImageViewExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -216,15 +216,15 @@ private final class ImageViewController {

// MARK: - Associating Controller

static var controllerAK: UInt8 = 0
static let controllerAK = malloc(1)!

// Lazily create a controller for a given view and associate it with a view.
static func controller(for view: ImageDisplayingView) -> ImageViewController {
if let controller = objc_getAssociatedObject(view, &ImageViewController.controllerAK) as? ImageViewController {
if let controller = objc_getAssociatedObject(view, controllerAK) as? ImageViewController {
return controller
}
let controller = ImageViewController(view: view)
objc_setAssociatedObject(view, &ImageViewController.controllerAK, controller, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_setAssociatedObject(view, controllerAK, controller, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return controller
}

Expand Down
6 changes: 3 additions & 3 deletions Tests/MockImageProcessor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ import Nuke
extension PlatformImage {
var nk_test_processorIDs: [String] {
get {
return (objc_getAssociatedObject(self, &AssociatedKeys.ProcessorIDs) as? [String]) ?? [String]()
return (objc_getAssociatedObject(self, AssociatedKeys.processorId) as? [String]) ?? [String]()
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.ProcessorIDs, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_setAssociatedObject(self, AssociatedKeys.processorId, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}

private enum AssociatedKeys {
static var ProcessorIDs: UInt8 = 0
static let processorId = malloc(1)!
}

// MARK: - MockImageProcessor
Expand Down
20 changes: 18 additions & 2 deletions Tests/NukeExtensionsTests/ImageViewExtensionsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import TVUIKit

#if os(iOS) || os(tvOS) || os(macOS) || os(visionOS)

@MainActor
class ImageViewExtensionsTests: XCTestCase {
var imageView: _ImageView!
var observer: ImagePipelineObserver!
Expand Down Expand Up @@ -44,7 +43,8 @@ class ImageViewExtensionsTests: XCTestCase {
}

// MARK: - Loading


@MainActor
func testImageLoaded() {
// When requesting an image with request
expectToLoadImage(with: Test.request, into: imageView)
Expand All @@ -55,6 +55,7 @@ class ImageViewExtensionsTests: XCTestCase {
}

#if os(tvOS)
@MainActor
func testImageLoadedToTVPosterView() {
// Use local instance for this tvOS specific test for simplicity
let posterView = TVPosterView()
Expand All @@ -68,6 +69,7 @@ class ImageViewExtensionsTests: XCTestCase {
}
#endif

@MainActor
func testImageLoadedWithURL() {
// When requesting an image with URL
let expectation = self.expectation(description: "Image loaded")
Expand All @@ -80,6 +82,7 @@ class ImageViewExtensionsTests: XCTestCase {
XCTAssertNotNil(imageView.image)
}

@MainActor
func testLoadImageWithNilRequest() {
// WHEN
imageView.image = Test.image
Expand All @@ -96,6 +99,7 @@ class ImageViewExtensionsTests: XCTestCase {
XCTAssertNil(imageView.image)
}

@MainActor
func testLoadImageWithNilRequestAndPlaceholder() {
// GIVEN
let failureImage = Test.image
Expand All @@ -111,6 +115,7 @@ class ImageViewExtensionsTests: XCTestCase {

// MARK: - Managing Tasks

@MainActor
func testTaskReturned() {
// When requesting an image
let task = NukeExtensions.loadImage(with: Test.request, into: imageView)
Expand All @@ -122,6 +127,7 @@ class ImageViewExtensionsTests: XCTestCase {
XCTAssertEqual(task?.request.urlRequest, Test.request.urlRequest)
}

@MainActor
func testTaskIsNilWhenImageInMemoryCache() {
// When the requested image is stored in memory cache
let request = Test.request
Expand All @@ -136,6 +142,7 @@ class ImageViewExtensionsTests: XCTestCase {

// MARK: - Prepare For Reuse

@MainActor
func testViewPreparedForReuse() {
// Given an image view displaying an image
imageView.image = Test.image
Expand All @@ -147,6 +154,7 @@ class ImageViewExtensionsTests: XCTestCase {
XCTAssertNil(imageView.image)
}

@MainActor
func testViewPreparedForReuseDisabled() {
// Given an image view displaying an image
let image = Test.image
Expand All @@ -163,6 +171,7 @@ class ImageViewExtensionsTests: XCTestCase {

// MARK: - Memory Cache

@MainActor
func testMemoryCacheUsed() {
// Given the requested image stored in memory cache
let image = Test.image
Expand All @@ -175,6 +184,7 @@ class ImageViewExtensionsTests: XCTestCase {
XCTAssertEqual(imageView.image, image)
}

@MainActor
func testMemoryCacheDisabled() {
// Given the requested image stored in memory cache
imageCache[Test.request] = Test.container
Expand All @@ -190,6 +200,7 @@ class ImageViewExtensionsTests: XCTestCase {

// MARK: - Completion and Progress Closures

@MainActor
func testCompletionCalled() {
var didCallCompletion = false
let expectation = self.expectation(description: "Image loaded")
Expand All @@ -210,6 +221,7 @@ class ImageViewExtensionsTests: XCTestCase {
wait()
}

@MainActor
func testCompletionCalledImageFromCache() {
// GIVEN the requested image stored in memory cache
imageCache[Test.request] = Test.container
Expand All @@ -228,6 +240,7 @@ class ImageViewExtensionsTests: XCTestCase {
XCTAssertTrue(didCallCompletion)
}

@MainActor
func testProgressHandlerCalled() {
// GIVEN
dataLoader.results[Test.url] = .success(
Expand All @@ -252,6 +265,7 @@ class ImageViewExtensionsTests: XCTestCase {

// MARK: - Cancellation

@MainActor
func testRequestCancelled() {
dataLoader.isSuspended = true

Expand All @@ -268,6 +282,7 @@ class ImageViewExtensionsTests: XCTestCase {
wait()
}

@MainActor
func testRequestCancelledWhenNewRequestStarted() {
dataLoader.isSuspended = true

Expand All @@ -283,6 +298,7 @@ class ImageViewExtensionsTests: XCTestCase {
wait()
}

@MainActor
func testRequestCancelledWhenTargetGetsDeallocated() {
dataLoader.isSuspended = true

Expand Down
8 changes: 6 additions & 2 deletions Tests/NukeExtensionsTests/ImageViewIntegrationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import XCTest

#if os(iOS) || os(tvOS) || os(macOS) || os(visionOS)

@MainActor
class ImageViewIntegrationTests: XCTestCase {
var imageView: _ImageView!
var pipeline: ImagePipeline!
Expand Down Expand Up @@ -44,6 +43,7 @@ class ImageViewIntegrationTests: XCTestCase {

// MARK: - Loading

@MainActor
func testImageLoaded() {
// When
expectToLoadImage(with: request, into: imageView)
Expand All @@ -52,7 +52,8 @@ class ImageViewIntegrationTests: XCTestCase {
// Then
XCTAssertNotNil(imageView.image)
}


@MainActor
func testImageLoadedWithURL() {
// When
let expectation = self.expectation(description: "Image loaded")
Expand All @@ -67,6 +68,7 @@ class ImageViewIntegrationTests: XCTestCase {

// MARK: - Loading with Invalid URL

@MainActor
func testLoadImageWithInvalidURLString() {
// WHEN
let expectation = self.expectation(description: "Image loaded")
Expand All @@ -80,6 +82,7 @@ class ImageViewIntegrationTests: XCTestCase {
XCTAssertNil(imageView.image)
}

@MainActor
func testLoadingWithNilURL() {
// GIVEN
var urlRequest = URLRequest(url: Test.url)
Expand Down Expand Up @@ -123,6 +126,7 @@ class ImageViewIntegrationTests: XCTestCase {
var recordedData = [Data?]()
}

@MainActor
func _testThatAttachedDataIsPassed() throws {
// GIVEN
pipeline = pipeline.reconfigured {
Expand Down
Loading

0 comments on commit 8bc6c59

Please sign in to comment.