Skip to content

Instantly share code, notes, and snippets.

@kean
Last active September 11, 2015 08:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kean/179797bf4bf3ba630611 to your computer and use it in GitHub Desktop.
Save kean/179797bf4bf3ba630611 to your computer and use it in GitHub Desktop.
Compose DFImageTask objects
// DFCompositeImageTask.h
#import "DFImageManagerDefines.h"
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@class DFCompositeImageTask;
@class DFImageRequest;
@class DFImageTask;
@protocol DFImageManaging;
typedef void (^DFCompositeImageTaskImageHandler)(UIImage *__nullable image, DFImageTask *__nonnull completedTask, DFCompositeImageTask *__nonnull compositeTask);
typedef void (^DFCompositeImageTaskCompletionHandler)(DFCompositeImageTask *__nonnull compositeTask);
/*! The DFCompositeImageTask manages execution of one or more image tasks and provides a single image handler that gets called multiple times. All requests are executed concurrently.
@note DFCompositeImageTask treats an array of image tasks as if the last one was the final image, while the others were the placeholders. The image handler gets called each time the image is successfully fetched, but it doesn't get called for obsolete tasks - when better image is already fetched. It also automatically cancels obsolete tasks.
@warning This class is not thread safe and is designed to be used on the main thread. All handlers are called on the main thread too.
*/
@interface DFCompositeImageTask : NSObject
/*! Initializes composite task with an array of image tasks. After you create the task, you must resume it by calling resume method.
@param tasks Array of image tasks. Must contain at least one task. All image tasks should be in suspended state.
@param imageHandler The image handler gets called each time the image is successfully fetched, but it doesn't get called for obsolete tasks.
@param completionHandler Completion handler that is executed after there are no remaining image tasks that are not either completed or cancelled.
*/
- (nonnull instancetype)initWithImageTasks:(nonnull NSArray /* DFImageTask */ *)tasks imageHandler:(nullable DFCompositeImageTaskImageHandler)imageHandler completionHandler:(nullable DFCompositeImageTaskCompletionHandler)completionHandler NS_DESIGNATED_INITIALIZER;
/*! Unavailable initializer, please use designated initializer.
*/
- (nullable instancetype)init NS_UNAVAILABLE;
/*! Creates image tasks with a given requests by using shared image manager. Then creates and returns composite image task. You must resume it by calling resume method.
@param requests Array of requests. Must contain at least one request.
@param imageHandler The image handler gets called each time the image is successfully fetched, but it doesn't get called for obsolete tasks.
@param completionHandler Completion handler that is executed after there are no remaining image tasks that are not either completed or cancelled.
*/
+ (nullable instancetype)compositeImageTaskWithRequests:(nonnull NSArray *)requests imageHandler:(nullable DFCompositeImageTaskImageHandler)imageHandler completionHandler:(nullable DFCompositeImageTaskCompletionHandler)completionHandler;
/*! Image handler. The image handler gets called each time the image is successfully fetched, but it doesn't get called for obsolete tasks.
*/
@property (nullable, nonatomic, copy) void (^imageHandler)(UIImage *__nullable image, DFImageTask *__nonnull completedTask, DFCompositeImageTask *__nonnull compositeTask);
/*! Completion handler that is executed after there are no remaining image tasks that are not either completed or cancelled.
*/
@property (nullable, nonatomic, copy) void (^completionHandler)(DFCompositeImageTask *__nonnull compositeTask);
/*! Set to YES to enable special handling of obsolete requests. Default value is YES.
*/
@property (nonatomic) BOOL allowsObsoleteRequests;
/*! Array of image tasks that the receiver was initialized with.
*/
@property (nonnull, nonatomic, copy, readonly) NSArray /* DFImageTask */ *imageTasks;
/*! Returns YES if all the requests have completed.
*/
@property (nonatomic, readonly) BOOL isFinished;
/*! Resumes the task.
*/
- (void)resume;
/*! Cancels all image tasks registered with the receiver. Removes image handler and completion handler.
*/
- (void)cancel;
/*! Sets the priority for all image tasks registered with a receiver.
*/
- (void)setPriority:(DFImageRequestPriority)priority;
@end
// DFCompositeImageTask.m
@implementation DFCompositeImageTask {
BOOL _isStarted;
NSMutableArray *_remainingTasks;
}
DF_INIT_UNAVAILABLE_IMPL
- (nonnull instancetype)initWithImageTasks:(nonnull NSArray *)tasks imageHandler:(nullable DFCompositeImageTaskImageHandler)imageHandler completionHandler:(nullable DFCompositeImageTaskCompletionHandler)completionHandler {
NSParameterAssert(tasks.count > 0);
if (self = [super init]) {
_imageTasks = [tasks copy];
_remainingTasks = [NSMutableArray arrayWithArray:tasks];
_imageHandler = imageHandler;
_completionHandler = completionHandler;
_allowsObsoleteRequests = YES;
}
return self;
}
+ (nullable DFCompositeImageTask *)compositeImageTaskWithRequests:(nonnull NSArray *)requests imageHandler:(nullable DFCompositeImageTaskImageHandler)imageHandler completionHandler:(nullable DFCompositeImageTaskCompletionHandler)completionHandler {
NSParameterAssert(requests.count > 0);
NSMutableArray *tasks = [NSMutableArray new];
for (DFImageRequest *request in requests) {
DFImageTask *task = [[DFImageManager sharedManager] imageTaskForRequest:request completion:nil];
if (task) {
[tasks addObject:task];
}
}
return tasks.count ? [[[self class] alloc] initWithImageTasks:tasks imageHandler:imageHandler completionHandler:completionHandler] : nil;
}
- (void)resume {
if (_isStarted) {
return;
}
_isStarted = YES;
typeof(self) __weak weakSelf = self;
for (DFImageTask *task in _remainingTasks) {
DFImageTaskCompletion completionHandler = task.completionHandler;
task.completionHandler = ^(UIImage *__nullable image, NSError *__nullable error, DFImageResponse *__nullable response, DFImageTask *__nonnull completedTask) {
[weakSelf _didFinishImageTask:completedTask withImage:image];
if (completionHandler) {
completionHandler(image, error, response, completedTask);
}
};
}
for (DFImageTask *task in [_remainingTasks copy]) {
[task resume];
}
}
- (BOOL)isFinished {
return _remainingTasks.count == 0;
}
- (void)cancel {
_imageHandler = nil;
_completionHandler = nil;
for (DFImageTask *task in [_remainingTasks copy]) {
[self _cancelTask:task];
}
}
- (void)setPriority:(DFImageRequestPriority)priority {
for (DFImageTask *task in _remainingTasks) {
task.priority = priority;
}
}
- (void)_didFinishImageTask:(nonnull DFImageTask *)task withImage:(nullable UIImage *)image {
if (![_remainingTasks containsObject:task]) {
return;
}
if (self.allowsObsoleteRequests) {
BOOL isSuccess = [self _isTaskSuccessfull:task];
BOOL isObsolete = [self _isTaskObsolete:task];
if (isSuccess) {
// Iterate through the 'left' subarray and cancel obsolete requests
NSArray *obsoleteTasks = [_remainingTasks objectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [_remainingTasks indexOfObject:task])]];
for (DFImageTask *obsoleteTask in obsoleteTasks) {
[self _cancelTask:obsoleteTask];
}
}
[_remainingTasks removeObject:task];
if (isSuccess && !isObsolete) {
if (_imageHandler) {
_imageHandler(image, task, self);
}
}
} else {
[_remainingTasks removeObject:task];
if (_imageHandler) {
_imageHandler(image, task, self);
}
}
if (self.isFinished) {
if (_completionHandler) {
_completionHandler(self);
}
}
}
- (void)_cancelTask:(nonnull DFImageTask *)task {
[_remainingTasks removeObject:task];
[task cancel];
}
- (BOOL)_isTaskSuccessfull:(nonnull DFImageTask *)task {
return task.state == DFImageTaskStateCompleted && task.error == nil;
}
/*! Returns YES if the request is obsolete. The request is considered obsolete if there is at least one successfully completed request in the 'right' subarray of the requests.
*/
- (BOOL)_isTaskObsolete:(nonnull DFImageTask *)task {
// Iterate throught the 'right' subarray of tasks
for (NSUInteger i = [_imageTasks indexOfObject:task] + 1; i < _imageTasks.count; i++) {
if ([self _isTaskSuccessfull:_imageTasks[i]]) {
return YES;
}
}
return NO;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment