Skip to content

Commit

Permalink
Merge pull request #25 from roubachof/feature/load-image-cancellation
Browse files Browse the repository at this point in the history
Add methods to cancel image loading
  • Loading branch information
roubachof authored Feb 20, 2025
2 parents 5751158 + 042a6fb commit f936777
Show file tree
Hide file tree
Showing 7 changed files with 319 additions and 86 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ jobs:
build:
runs-on: macOS-15
env:
XCODE_VERSION: 16
dotnetVersion: 9.0.100
XCODE_VERSION: 16.2
dotnetVersion: 9.0.200

steps:
- name: Checkout
Expand Down
166 changes: 105 additions & 61 deletions NukeProxy/NukeProxy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import Nuke

@objc(ImagePipeline)
public class ImagePipeline : NSObject {

@objc
public static let shared = ImagePipeline()

Expand All @@ -22,6 +21,9 @@ public class ImagePipeline : NSObject {
Nuke.ImagePipeline.shared = Nuke.ImagePipeline(configuration: .withDataCache)
}

private var tasks: [ImageTask] = []
private let taskLock = NSLock()

@objc
public func isCached(for url: URL) -> Bool {
return Nuke.ImagePipeline.shared.cache.containsCachedImage(for: ImageRequest(url: url))
Expand All @@ -47,94 +49,137 @@ public class ImagePipeline : NSObject {
public func removeAllCaches() {
Nuke.ImagePipeline.shared.cache.removeAll()
}

@objc
public func loadImage(url: URL, onCompleted: @escaping (UIImage?, String) -> Void) {
_ = Nuke.ImagePipeline.shared.loadImage(
public func loadImage(url: URL, onCompleted: @escaping (UIImage?, String) -> Void) -> Int64 {
let task = Nuke.ImagePipeline.shared.loadImage(
with: url,
progress: nil,
completion: { result in
completion: { [weak self] result in
switch result {
case let .success(response):
onCompleted(response.image, "success")
case let .failure(error):
onCompleted(nil, error.localizedDescription)
}
self?.removeAllTasksForUrl(url.absoluteString)
}
)

return addTask(task)
}

@objc
public func loadImage(url: URL, placeholder: UIImage?, errorImage: UIImage?, into: UIImageView) {
loadImage(url: url, placeholder: placeholder, errorImage: errorImage, into: into, reloadIgnoringCachedData: false)
public func loadImage(url: URL, placeholder: UIImage?, errorImage: UIImage?, into: UIImageView) -> Int64 {
return loadImage(url: url, placeholder: placeholder, errorImage: errorImage, into: into, reloadIgnoringCachedData: false)
}

@objc
public func loadImage(url: URL, placeholder: UIImage?, errorImage: UIImage?, into: UIImageView, reloadIgnoringCachedData: Bool) {
let options = ImageLoadingOptions(placeholder:placeholder, failureImage: errorImage)
Nuke.loadImage(
with: ImageRequest(
urlRequest: URLRequest(
url: url,
cachePolicy: reloadIgnoringCachedData ?
URLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData :
URLRequest.CachePolicy.useProtocolCachePolicy
)
),
public func loadImage(url: URL, placeholder: UIImage?, errorImage: UIImage?, into: UIImageView, reloadIgnoringCachedData: Bool) -> Int64 {
let options = ImageLoadingOptions(placeholder: placeholder, failureImage: errorImage)
let cachePolicy = reloadIgnoringCachedData ? URLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData : URLRequest.CachePolicy.useProtocolCachePolicy
let urlRequest = URLRequest(url: url, cachePolicy: cachePolicy)
let request = ImageRequest(urlRequest: urlRequest)

let task = Nuke.loadImage(
with: request,
options: options,
into: into)
into: into
)

return addTask(task)
}

@objc
public func loadImage(url: URL, imageIdKey: String, placeholder: UIImage?, errorImage: UIImage?, into: UIImageView) {
loadImage(url: url, imageIdKey: imageIdKey, placeholder: placeholder, errorImage: errorImage, into: into, reloadIgnoringCachedData: false)
public func loadImage(url: URL, imageIdKey: String, placeholder: UIImage?, errorImage: UIImage?, into: UIImageView) -> Int64 {
return loadImage(url: url, imageIdKey: imageIdKey, placeholder: placeholder, errorImage: errorImage, into: into, reloadIgnoringCachedData: false)
}

@objc
public func loadImage(url: URL, imageIdKey: String, placeholder: UIImage?, errorImage: UIImage?, into: UIImageView, reloadIgnoringCachedData: Bool) {
public func loadImage(url: URL, imageIdKey: String, placeholder: UIImage?, errorImage: UIImage?, into: UIImageView, reloadIgnoringCachedData: Bool) -> Int64 {
let options = ImageLoadingOptions(placeholder: placeholder, failureImage: errorImage)

Nuke.loadImage(
with: ImageRequest(
urlRequest: URLRequest(
url: url,
cachePolicy: reloadIgnoringCachedData ?
URLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData :
URLRequest.CachePolicy.useProtocolCachePolicy
),
userInfo: [.imageIdKey: imageIdKey ]
),
let cachePolicy = reloadIgnoringCachedData ? URLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData : URLRequest.CachePolicy.useProtocolCachePolicy
let urlRequest = URLRequest(url: url, cachePolicy: cachePolicy)
let request = ImageRequest(urlRequest: urlRequest, userInfo: [.imageIdKey: imageIdKey])

let task = Nuke.loadImage(
with: request,
options: options,
into: into
)
}

return addTask(task)
}

@objc
public func loadData(url: URL, onCompleted: @escaping (Data?, URLResponse?) -> Void) {
loadData(url: url, imageIdKey: nil, reloadIgnoringCachedData: false, onCompleted: onCompleted)
public func loadData(url: URL, onCompleted: @escaping (Data?, URLResponse?) -> Void) -> Int64 {
return loadData(url: url, imageIdKey: nil, reloadIgnoringCachedData: false, onCompleted: onCompleted)
}

@objc
public func loadData(url: URL, imageIdKey: String?, reloadIgnoringCachedData: Bool, onCompleted: @escaping (Data?, URLResponse?) -> Void) {
_ = Nuke.ImagePipeline.shared.loadData(
with: ImageRequest(
urlRequest: URLRequest(
url: url,
cachePolicy: reloadIgnoringCachedData ?
URLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData :
URLRequest.CachePolicy.useProtocolCachePolicy
),
userInfo: imageIdKey == nil ? nil : [.imageIdKey: imageIdKey! ]
),
completion: { result in
public func loadData(url: URL, imageIdKey: String?, reloadIgnoringCachedData: Bool, onCompleted: @escaping (Data?, URLResponse?) -> Void) -> Int64 {
let cachePolicy = reloadIgnoringCachedData ? URLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData : URLRequest.CachePolicy.useProtocolCachePolicy
let urlRequest = URLRequest(url: url, cachePolicy: cachePolicy)
let request = ImageRequest(urlRequest: urlRequest, userInfo: imageIdKey == nil ? nil : [.imageIdKey: imageIdKey!])

let task = Nuke.ImagePipeline.shared.loadData(
with: request,
completion: { [weak self] result in
switch result {
case let .success(response):
onCompleted(response.data, response.response)
case .failure(_):
onCompleted(nil, nil)
}
self?.removeAllTasksForUrl(url.absoluteString)
}
)

return addTask(task)
}

@objc
public func cancelTasksForUrl(_ url: String) {
var cancelledTasks = [ImageTask]()

taskLock.lock()
tasks.forEach { task in
if (task.request.imageId == url) {
task.cancel()
cancelledTasks.append(task)
}
}

tasks.removeAll { task in
cancelledTasks.contains(task)
}
taskLock.unlock()
}

@objc
public func cancelTask(_ taskId: Int64) {
taskLock.lock()
tasks.first(where: { $0.taskId == taskId })?.cancel()
tasks.removeAll(where: { $0.taskId == taskId })
taskLock.unlock()
}

private func addTask(_ task: ImageTask?) -> Int64 {
guard let task else {
return -1
}

taskLock.lock()
tasks.append(task)
taskLock.unlock()

return task.taskId
}

private func removeAllTasksForUrl(_ url: String) {
taskLock.lock()
tasks.removeAll(where: { $0.request.imageId == url })
taskLock.unlock()
}
}

Expand Down Expand Up @@ -162,7 +207,7 @@ public final class DataLoader: NSObject {

@objc(Prefetcher)
public final class Prefetcher: NSObject {

private var prefetcher: ImagePrefetcher

@objc
Expand All @@ -173,17 +218,16 @@ public final class Prefetcher: NSObject {
@objc
public init(destination: Destination = .memoryCache) {
prefetcher = ImagePrefetcher(destination: destination == .memoryCache ?
ImagePrefetcher.Destination.memoryCache :
ImagePrefetcher.Destination.memoryCache :
ImagePrefetcher.Destination.diskCache)
}

@objc
public init(destination: Destination = .memoryCache,
maxConcurrentRequestCount: Int = 2) {
prefetcher = ImagePrefetcher(destination: destination == .memoryCache ?
ImagePrefetcher.Destination.memoryCache :
ImagePrefetcher.Destination.diskCache,
maxConcurrentRequestCount: maxConcurrentRequestCount)
public init(destination: Destination = .memoryCache, maxConcurrentRequestCount: Int = 2) {
prefetcher = ImagePrefetcher(
destination: destination == .memoryCache ? ImagePrefetcher.Destination.memoryCache : ImagePrefetcher.Destination.diskCache,
maxConcurrentRequestCount: maxConcurrentRequestCount
)
}

@objc
Expand Down
4 changes: 4 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/bin/bash

mkdir -p artifacts

echo "Update carthage deps"
sh carthage.sh update --use-xcframeworks --platform iOS --log-path artifacts/carthage.log

Expand All @@ -8,6 +10,8 @@ echo 'exit 0' > Carthage/Checkouts/Nuke/Scripts/validate.sh

sh carthage.sh build --use-xcframeworks --platform iOS --log-path artifacts/carthage.log

rm -r Output/NukeProxy.xcframework

echo "xcode build"
xcodebuild archive -sdk iphoneos -project NukeProxy.xcodeproj -scheme NukeProxy -configuration Release -archivePath Output/Output-iphoneos SKIP_INSTALL=NO
xcodebuild archive -sdk iphonesimulator -project NukeProxy.xcodeproj -scheme NukeProxy -configuration Release -archivePath Output/Output-iphonesimulator SKIP_INSTALL=NO
Expand Down
38 changes: 23 additions & 15 deletions src/ImageCaching.Nuke/ApiDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,33 +65,41 @@ interface ImagePipeline
[Export ("removeAllCaches")]
void RemoveAllCaches ();

// -(void)loadImageWithUrl:(NSURL * _Nonnull)url onCompleted:(void (^ _Nonnull)(UIImage * _Nullable, NSString * _Nonnull))onCompleted;
// -(Int64)loadImageWithUrl:(NSURL * _Nonnull)url onCompleted:(void (^ _Nonnull)(UIImage * _Nullable, NSString * _Nonnull))onCompleted;
[Export ("loadImageWithUrl:onCompleted:")]
void LoadImageWithUrl (NSUrl url, Action<UIImage, NSString> onCompleted);
long LoadImageWithUrl (NSUrl url, Action<UIImage, NSString> onCompleted);

// -(void)loadImageWithUrl:(NSURL * _Nonnull)url placeholder:(UIImage * _Nullable)placeholder errorImage:(UIImage * _Nullable)errorImage into:(UIImageView * _Nonnull)into;
// -(Int64)loadImageWithUrl:(NSURL * _Nonnull)url placeholder:(UIImage * _Nullable)placeholder errorImage:(UIImage * _Nullable)errorImage into:(UIImageView * _Nonnull)into;
[Export ("loadImageWithUrl:placeholder:errorImage:into:")]
void LoadImageWithUrl (NSUrl url, [NullAllowed] UIImage placeholder, [NullAllowed] UIImage errorImage, UIImageView into);
long LoadImageWithUrl (NSUrl url, [NullAllowed] UIImage placeholder, [NullAllowed] UIImage errorImage, UIImageView into);

// -(void)loadImageWithUrl:(NSURL * _Nonnull)url placeholder:(UIImage * _Nullable)placeholder errorImage:(UIImage * _Nullable)errorImage into:(UIImageView * _Nonnull)into reloadIgnoringCachedData:(BOOL)reloadIgnoringCachedData;
// -(Int64)loadImageWithUrl:(NSURL * _Nonnull)url placeholder:(UIImage * _Nullable)placeholder errorImage:(UIImage * _Nullable)errorImage into:(UIImageView * _Nonnull)into reloadIgnoringCachedData:(BOOL)reloadIgnoringCachedData;
[Export ("loadImageWithUrl:placeholder:errorImage:into:reloadIgnoringCachedData:")]
void LoadImageWithUrl (NSUrl url, [NullAllowed] UIImage placeholder, [NullAllowed] UIImage errorImage, UIImageView into, bool reloadIgnoringCachedData);
long LoadImageWithUrl (NSUrl url, [NullAllowed] UIImage placeholder, [NullAllowed] UIImage errorImage, UIImageView into, bool reloadIgnoringCachedData);

// -(void)loadImageWithUrl:(NSURL * _Nonnull)url imageIdKey:(NSString * _Nonnull)imageIdKey placeholder:(UIImage * _Nullable)placeholder errorImage:(UIImage * _Nullable)errorImage into:(UIImageView * _Nonnull)into;
// -(Int64)loadImageWithUrl:(NSURL * _Nonnull)url imageIdKey:(NSString * _Nonnull)imageIdKey placeholder:(UIImage * _Nullable)placeholder errorImage:(UIImage * _Nullable)errorImage into:(UIImageView * _Nonnull)into;
[Export ("loadImageWithUrl:imageIdKey:placeholder:errorImage:into:")]
void LoadImageWithUrl (NSUrl url, string imageIdKey, [NullAllowed] UIImage placeholder, [NullAllowed] UIImage errorImage, UIImageView into);
long LoadImageWithUrl (NSUrl url, string imageIdKey, [NullAllowed] UIImage placeholder, [NullAllowed] UIImage errorImage, UIImageView into);

// -(void)loadImageWithUrl:(NSURL * _Nonnull)url imageIdKey:(NSString * _Nonnull)imageIdKey placeholder:(UIImage * _Nullable)placeholder errorImage:(UIImage * _Nullable)errorImage into:(UIImageView * _Nonnull)into reloadIgnoringCachedData:(BOOL)reloadIgnoringCachedData;
// -(Int64)loadImageWithUrl:(NSURL * _Nonnull)url imageIdKey:(NSString * _Nonnull)imageIdKey placeholder:(UIImage * _Nullable)placeholder errorImage:(UIImage * _Nullable)errorImage into:(UIImageView * _Nonnull)into reloadIgnoringCachedData:(BOOL)reloadIgnoringCachedData;
[Export ("loadImageWithUrl:imageIdKey:placeholder:errorImage:into:reloadIgnoringCachedData:")]
void LoadImageWithUrl (NSUrl url, string imageIdKey, [NullAllowed] UIImage placeholder, [NullAllowed] UIImage errorImage, UIImageView into, bool reloadIgnoringCachedData);
long LoadImageWithUrl (NSUrl url, string imageIdKey, [NullAllowed] UIImage placeholder, [NullAllowed] UIImage errorImage, UIImageView into, bool reloadIgnoringCachedData);

// -(void)loadDataWithUrl:(NSURL * _Nonnull)url onCompleted:(void (^ _Nonnull)(NSData * _Nullable, NSUrlResponse * _Nullable))onCompleted;
// -(Int64)loadDataWithUrl:(NSURL * _Nonnull)url onCompleted:(void (^ _Nonnull)(NSData * _Nullable, NSUrlResponse * _Nullable))onCompleted;
[Export ("loadDataWithUrl:onCompleted:")]
void LoadDataWithUrl (NSUrl url, Action<NSData, NSUrlResponse> onCompleted);
long LoadDataWithUrl (NSUrl url, Action<NSData, NSUrlResponse> onCompleted);

// -(void)loadDataWithUrl:(NSURL * _Nonnull)url imageIdKey:(NSString * _Nullable)imageIdKey reloadIgnoringCachedData:(BOOL)reloadIgnoringCachedData onCompleted:(void (^ _Nonnull)(NSData * _Nullable, NSUrlResponse * _Nullable))onCompleted;
// -(Int64)loadDataWithUrl:(NSURL * _Nonnull)url imageIdKey:(NSString * _Nullable)imageIdKey reloadIgnoringCachedData:(BOOL)reloadIgnoringCachedData onCompleted:(void (^ _Nonnull)(NSData * _Nullable, NSUrlResponse * _Nullable))onCompleted;
[Export ("loadDataWithUrl:imageIdKey:reloadIgnoringCachedData:onCompleted:")]
void LoadDataWithUrl (NSUrl url, [NullAllowed] string imageIdKey, bool reloadIgnoringCachedData, Action<NSData, NSUrlResponse> onCompleted);
long LoadDataWithUrl (NSUrl url, [NullAllowed] string imageIdKey, bool reloadIgnoringCachedData, Action<NSData, NSUrlResponse> onCompleted);

// -(Int64)cancelTasksForUrl:(NSString * _Nonnull)url;
[Export ("cancelTasksForUrl:")]
void CancelTasksForUrl (string url);

// -(void)cancelTask:(Int64 * _Nonnull)taskId;
[Export ("cancelTask:")]
void CancelTask (long taskId);
}

// @interface Prefetcher : NSObject
Expand Down Expand Up @@ -128,4 +136,4 @@ interface Prefetcher
[Export ("unPause")]
void UnPause ();
}
}
}
Loading

0 comments on commit f936777

Please sign in to comment.