Skip to content

Commit

Permalink
fix: update nuke to latest
Browse files Browse the repository at this point in the history
  • Loading branch information
gtokman committed Nov 2, 2024
1 parent 1dccbd1 commit 4a76124
Show file tree
Hide file tree
Showing 24 changed files with 166 additions and 95 deletions.
12 changes: 7 additions & 5 deletions ios/Nuke/Decoding/ImageDecoders+Default.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ extension ImageDecoders {
private var scanner = ProgressiveJPEGScanner()

private var isPreviewForGIFGenerated = false
private var scale: CGFloat?
private var scale: CGFloat = 1.0
private var thumbnail: ImageRequest.ThumbnailOptions?
private let lock = NSLock()

Expand All @@ -38,7 +38,7 @@ extension ImageDecoders {
/// Returns `nil` if progressive decoding is not allowed for the given
/// content.
public init?(context: ImageDecodingContext) {
self.scale = context.request.scale.map { CGFloat($0) }
self.scale = context.request.scale.map { CGFloat($0) } ?? self.scale
self.thumbnail = context.request.thumbnail

if !context.isCompleted && !isProgressiveDecodingAllowed(for: context.data) {
Expand All @@ -52,7 +52,9 @@ extension ImageDecoders {

func makeImage() -> PlatformImage? {
if let thumbnail {
return makeThumbnail(data: data, options: thumbnail)
return makeThumbnail(data: data,
options: thumbnail,
scale: scale)
}
return ImageDecoders.Default._decode(data, scale: scale)
}
Expand Down Expand Up @@ -162,11 +164,11 @@ private struct ProgressiveJPEGScanner: Sendable {
}

extension ImageDecoders.Default {
private static func _decode(_ data: Data, scale: CGFloat?) -> PlatformImage? {
private static func _decode(_ data: Data, scale: CGFloat) -> PlatformImage? {
#if os(macOS)
return NSImage(data: data)
#else
return UIImage(data: data, scale: scale ?? 1)
return UIImage(data: data, scale: scale)
#endif
}
}
Expand Down
3 changes: 1 addition & 2 deletions ios/Nuke/ImageRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -357,8 +357,7 @@ public struct ImageRequest: CustomStringConvertible, Sendable, ExpressibleByStri
/// ```
public static let imageIdKey: ImageRequest.UserInfoKey = "github.com/kean/nuke/imageId"

/// The image scale to be used. By default, the scale matches the scale
/// of the current display.
/// The image scale to be used. By default, the scale is `1`.
public static let scaleKey: ImageRequest.UserInfoKey = "github.com/kean/nuke/scale"

/// Specifies whether the pipeline should retrieve or generate a thumbnail
Expand Down
4 changes: 2 additions & 2 deletions ios/Nuke/ImageTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// Copyright (c) 2015-2024 Alexander Grebenyuk (github.com/kean).

import Foundation
import Combine
@preconcurrency import Combine

#if canImport(UIKit)
import UIKit
Expand Down Expand Up @@ -283,7 +283,7 @@ public typealias AsyncImageTask = ImageTask
// MARK: - ImageTask (Private)

extension ImageTask {
private func makeStream<T>(of closure: @escaping (Event) -> T?) -> AsyncStream<T> {
private func makeStream<T>(of closure: @Sendable @escaping (Event) -> T?) -> AsyncStream<T> {
AsyncStream { continuation in
self.queue.async {
guard let events = self._makeEventsSubject() else {
Expand Down
10 changes: 8 additions & 2 deletions ios/Nuke/Internal/DataPublisher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,13 @@ final class DataPublisher {
private func publisher(from closure: @Sendable @escaping () async throws -> Data) -> AnyPublisher<Data, Error> {
Deferred {
Future { promise in
let promise = UncheckedSendableBox(value: promise)
Task {
do {
let data = try await closure()
promise(.success(data))
promise.value(.success(data))
} catch {
promise(.failure(error))
promise.value(.failure(error))
}
}
}
Expand All @@ -52,3 +53,8 @@ enum PublisherCompletion {
case finished
case failure(Error)
}

/// - warning: Avoid using it!
struct UncheckedSendableBox<Value>: @unchecked Sendable {
let value: Value
}
64 changes: 42 additions & 22 deletions ios/Nuke/Internal/Graphics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -160,42 +160,35 @@ extension PlatformImage {

private extension CGContext {
static func make(_ image: CGImage, size: CGSize, alphaInfo: CGImageAlphaInfo? = nil) -> CGContext? {
let alphaInfo: CGImageAlphaInfo = alphaInfo ?? preferredAlphaInfo(for: image)

// Create the context which matches the input image.
if let ctx = CGContext(
data: nil,
width: Int(size.width),
height: Int(size.height),
bitsPerComponent: 8,
bytesPerRow: 0,
space: image.colorSpace ?? CGColorSpaceCreateDeviceRGB(),
bitmapInfo: alphaInfo.rawValue
) {
if let ctx = CGContext.make(image, size: size, alphaInfo: alphaInfo, colorSpace: image.colorSpace ?? CGColorSpaceCreateDeviceRGB()) {
return ctx
}

// In case the combination of parameters (color space, bits per component, etc)
// is nit supported by Core Graphics, switch to default context.
// - Quartz 2D Programming Guide
// - https://github.com/kean/Nuke/issues/35
// - https://github.com/kean/Nuke/issues/57
return CGContext(
return CGContext.make(image, size: size, alphaInfo: alphaInfo, colorSpace: CGColorSpaceCreateDeviceRGB())
}

static func make(_ image: CGImage, size: CGSize, alphaInfo: CGImageAlphaInfo?, colorSpace: CGColorSpace) -> CGContext? {
CGContext(
data: nil,
width: Int(size.width), height: Int(size.height),
width: Int(size.width),
height: Int(size.height),
bitsPerComponent: 8,
bytesPerRow: 0,
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: alphaInfo.rawValue
space: colorSpace,
bitmapInfo: (alphaInfo ?? preferredAlphaInfo(for: image, colorSpace: colorSpace)).rawValue
)
}

/// - See https://developer.apple.com/library/archive/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_context/dq_context.html#//apple_ref/doc/uid/TP30001066-CH203-BCIBHHBB
private static func preferredAlphaInfo(for image: CGImage) -> CGImageAlphaInfo {
private static func preferredAlphaInfo(for image: CGImage, colorSpace: CGColorSpace) -> CGImageAlphaInfo {
guard image.isOpaque else {
return .premultipliedLast
}
if image.bitsPerPixel == 8 {
if colorSpace.numberOfComponents == 1 && image.bitsPerPixel == 8 {
return .none // The only pixel format supported for grayscale CS
}
return .noneSkipLast
Expand Down Expand Up @@ -251,9 +244,22 @@ extension CGImagePropertyOrientation {
}
}
}
#endif

#if canImport(UIKit)
extension UIImage.Orientation {
init(_ cgOrientation: CGImagePropertyOrientation) {
switch cgOrientation {
case .up: self = .up
case .upMirrored: self = .upMirrored
case .down: self = .down
case .downMirrored: self = .downMirrored
case .left: self = .left
case .leftMirrored: self = .leftMirrored
case .right: self = .right
case .rightMirrored: self = .rightMirrored
}
}
}

private extension CGSize {
func rotatedForOrientation(_ imageOrientation: CGImagePropertyOrientation) -> CGSize {
switch imageOrientation {
Expand Down Expand Up @@ -358,7 +364,10 @@ extension Color {
}

/// Creates an image thumbnail. Uses significantly less memory than other options.
func makeThumbnail(data: Data, options: ImageRequest.ThumbnailOptions) -> PlatformImage? {
/// - parameter data: Data object from which to read the image.
/// - parameter options: Image loading options.
/// - parameter scale: The scale factor to assume when interpreting the image data, defaults to 1.
func makeThumbnail(data: Data, options: ImageRequest.ThumbnailOptions, scale: CGFloat = 1.0) -> PlatformImage? {
guard let source = CGImageSourceCreateWithData(data as CFData, [kCGImageSourceShouldCache: false] as CFDictionary) else {
return nil
}
Expand All @@ -373,7 +382,18 @@ func makeThumbnail(data: Data, options: ImageRequest.ThumbnailOptions) -> Platfo
guard let image = CGImageSourceCreateThumbnailAtIndex(source, 0, options as CFDictionary) else {
return nil
}

#if canImport(UIKit)
var orientation: UIImage.Orientation = .up
if let imageProperties = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [AnyHashable: Any],
let orientationValue = imageProperties[kCGImagePropertyOrientation as String] as? UInt32,
let cgOrientation = CGImagePropertyOrientation(rawValue: orientationValue) {
orientation = UIImage.Orientation(cgOrientation)
}
return PlatformImage(cgImage: image, scale: scale, orientation: orientation)
#else
return PlatformImage(cgImage: image)
#endif
}

private func getMaxPixelSize(for source: CGImageSource, options thumbnailOptions: ImageRequest.ThumbnailOptions) -> CGFloat {
Expand Down
1 change: 0 additions & 1 deletion ios/Nuke/Internal/ImagePublisher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ private final class ImageSubscription<S>: Subscription where S: Subscriber, S: S

task = pipeline.loadImage(
with: request,
queue: nil,
progress: { response, _, _ in
if let response {
// Send progressively decoded image (if enabled and if any)
Expand Down
4 changes: 1 addition & 3 deletions ios/Nuke/Internal/Log.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,12 @@ func signpost<T>(_ name: StaticString, _ work: () throws -> T) rethrows -> T {

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

private let byteFormatter = ByteCountFormatter()

enum Formatter {
static func bytes(_ count: Int) -> String {
bytes(Int64(count))
}

static func bytes(_ count: Int64) -> String {
byteFormatter.string(fromByteCount: count)
ByteCountFormatter().string(fromByteCount: count)
}
}
2 changes: 1 addition & 1 deletion ios/Nuke/Internal/Operation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import Foundation

final class Operation: Foundation.Operation {
final class Operation: Foundation.Operation, @unchecked Sendable {
override var isExecuting: Bool {
get {
os_unfair_lock_lock(lock)
Expand Down
26 changes: 14 additions & 12 deletions ios/Nuke/Loading/DataLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Foundation
/// Provides basic networking using `URLSession`.
public final class DataLoader: DataLoading, @unchecked Sendable {
public let session: URLSession
private let impl = _DataLoader()
private let impl: _DataLoader

/// Determines whether to deliver a partial response body in increments. By
/// default, `false`.
Expand Down Expand Up @@ -41,12 +41,12 @@ public final class DataLoader: DataLoading, @unchecked Sendable {
/// - validate: Validates the response. By default, check if the status
/// code is in the acceptable range (`200..<300`).
public init(configuration: URLSessionConfiguration = DataLoader.defaultConfiguration,
validate: @escaping (URLResponse) -> Swift.Error? = DataLoader.validate) {
validate: @Sendable @escaping (URLResponse) -> Swift.Error? = DataLoader.validate) {
self.impl = _DataLoader(validate: validate)
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
self.session = URLSession(configuration: configuration, delegate: impl, delegateQueue: queue)
self.session.sessionDescription = "Nuke URLSession"
self.impl.validate = validate
}

/// Returns a default configuration which has a `sharedUrlCache` set
Expand All @@ -59,7 +59,7 @@ public final class DataLoader: DataLoading, @unchecked Sendable {

/// Validates `HTTP` responses by checking that the status code is 2xx. If
/// it's not returns ``DataLoader/Error/statusCodeUnacceptable(_:)``.
public static func validate(response: URLResponse) -> Swift.Error? {
@Sendable public static func validate(response: URLResponse) -> Swift.Error? {
guard let response = response as? HTTPURLResponse else {
return nil
}
Expand Down Expand Up @@ -117,11 +117,15 @@ public final class DataLoader: DataLoading, @unchecked Sendable {
// Actual data loader implementation. Hide NSObject inheritance, hide
// URLSessionDataDelegate conformance, and break retain cycle between URLSession
// and URLSessionDataDelegate.
private final class _DataLoader: NSObject, URLSessionDataDelegate {
var validate: (URLResponse) -> Swift.Error? = DataLoader.validate
private final class _DataLoader: NSObject, URLSessionDataDelegate, @unchecked Sendable {
let validate: @Sendable (URLResponse) -> Swift.Error?
private var handlers = [URLSessionTask: _Handler]()
var delegate: URLSessionDelegate?

init(validate: @Sendable @escaping (URLResponse) -> Swift.Error?) {
self.validate = validate
}

/// Loads data with the given request.
func loadData(with task: URLSessionDataTask,
session: URLSession,
Expand All @@ -131,7 +135,6 @@ private final class _DataLoader: NSObject, URLSessionDataDelegate {
session.delegateQueue.addOperation { // `URLSession` is configured to use this same queue
self.handlers[task] = handler
}
task.taskDescription = "Nuke Load Data"
task.resume()
return AnonymousCancellable { task.cancel() }
}
Expand Down Expand Up @@ -180,21 +183,20 @@ private final class _DataLoader: NSObject, URLSessionDataDelegate {
(delegate as? URLSessionTaskDelegate)?.urlSession?(session, task: task, didFinishCollecting: metrics)
}

func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) {
(delegate as? URLSessionTaskDelegate)?.urlSession?(session, task: task, willPerformHTTPRedirection: response, newRequest: request, completionHandler: completionHandler) ??
completionHandler(request)
func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @Sendable @escaping (URLRequest?) -> Void) {
(delegate as? URLSessionTaskDelegate)?.urlSession?(session, task: task, willPerformHTTPRedirection: response, newRequest: request, completionHandler: completionHandler) ?? completionHandler(request)
}

func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) {
(delegate as? URLSessionTaskDelegate)?.urlSession?(session, taskIsWaitingForConnectivity: task)
}

func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @Sendable @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
(delegate as? URLSessionTaskDelegate)?.urlSession?(session, task: task, didReceive: challenge, completionHandler: completionHandler) ??
completionHandler(.performDefaultHandling, nil)
}

func urlSession(_ session: URLSession, task: URLSessionTask, willBeginDelayedRequest request: URLRequest, completionHandler: @escaping (URLSession.DelayedRequestDisposition, URLRequest?) -> Void) {
func urlSession(_ session: URLSession, task: URLSessionTask, willBeginDelayedRequest request: URLRequest, completionHandler: @Sendable @escaping (URLSession.DelayedRequestDisposition, URLRequest?) -> Void) {
(delegate as? URLSessionTaskDelegate)?.urlSession?(session, task: task, willBeginDelayedRequest: request, completionHandler: completionHandler) ??
completionHandler(.continueLoading, nil)
}
Expand Down
8 changes: 7 additions & 1 deletion ios/Nuke/Pipeline/ImagePipeline+Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,13 @@ extension ImagePipeline {

/// A queue on which all callbacks, like `progress` and `completion`
/// callbacks are called. `.main` by default.
public var callbackQueue = DispatchQueue.main
@available(*, deprecated, message: "`ImagePipeline` no longer supports changing the callback queue")
public var callbackQueue: DispatchQueue {
get { _callbackQueue }
set { _callbackQueue = newValue }
}

var _callbackQueue = DispatchQueue.main

// MARK: - Options (Shared)

Expand Down
Loading

0 comments on commit 4a76124

Please sign in to comment.