Skip to content

Instantly share code, notes, and snippets.

@joerick
Last active August 29, 2015 14:17
Show Gist options
  • Save joerick/e4d2c99a2127715d9bc3 to your computer and use it in GitHub Desktop.
Save joerick/e4d2c99a2127715d9bc3 to your computer and use it in GitHub Desktop.
MXSuperOperation
//
// MXCheckedOperation.h
// Mixim
//
// Created by Joe Rickerby on 25/03/2014.
// Copyright (c) 2014 Mixim Technology Ltd. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
`MXCheckedOperation` is an operation that ends in failure or success.
Subclasses should override `-mainError:` and provide an implementation.
Alternatively concurrent subclasses can override `-start`, but they must set the `success` and `error` properties, as well as the NSOperation properties, such as `isFinished` etc.
*/
@interface MXCheckedOperation : NSOperation
- (BOOL)mainError:(NSError **)error;
@property (strong) NSError *error;
@property (assign) BOOL success;
- (void)setCompletionBlockWithSuccess:(void (^)(id operation))success
failure:(void (^)(id operation, NSError *error))failure;
@end
//
// MXCheckedOperation.m
// Mixim
//
// Created by Joe Rickerby on 25/03/2014.
// Copyright (c) 2014 Mixim Technology Ltd. All rights reserved.
//
#import "MXCheckedOperation.h"
@interface MXCheckedOperation ()
@property (copy) void (^successHandler)(id operation);
@property (copy) void (^failureHandler)(id operation, NSError *error);
@end
@implementation MXCheckedOperation
- (id)init
{
self = [super init];
if (self) {
__typeof__(self) __weak weakSelf = self;
self.completionBlock = ^{
if (weakSelf.success) {
if (weakSelf.successHandler) {
weakSelf.successHandler(weakSelf);
}
} else {
if (weakSelf.failureHandler) {
weakSelf.failureHandler(weakSelf, weakSelf.error);
}
}
};
}
return self;
}
- (void)main
{
NSError *error = nil;
self.success = [self mainError:&error];
self.error = error;
}
- (BOOL)mainError:(NSError *__autoreleasing *)error
{
[NSException raise:NSGenericException format:@"Subclasses of %@ should override %s",
[MXCheckedOperation class], __PRETTY_FUNCTION__];
return NO;
}
- (void)setCompletionBlockWithSuccess:(void (^)(id))success failure:(void (^)(id, NSError *))failure
{
self.successHandler = success;
self.failureHandler = failure;
}
@end
//
// MXSuperOperation.h
// Mixim
//
// Created by Joe Rickerby on 10/04/2014.
// Copyright (c) 2014 Mixim Technology Ltd. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MXCheckedOperation.h"
@interface MXSuperOperation : MXCheckedOperation
- (void)addSuboperation:(MXCheckedOperation *)operation;
@property (strong, readonly) NSArray *suboperations;
@property (assign, readonly) BOOL suboperationsSuccess;
@property (strong, readonly) NSArray *suboperationErrors;
/**
* Subclasses should implement to start the operation and call -addSuboperation:
*/
- (BOOL)startError:(NSError **)error;
/**
* Subclasses can implement to be notified when a suboperation finishes
*/
- (void)suboperationComplete:(MXCheckedOperation *)operation;
/**
* Subclasses can implement to be notified when a suboperation fails
*/
- (void)suboperation:(MXCheckedOperation *)operation failedWithError:(NSError *)error;
/**
* Subclasses can implement to be notified when a suboperation finishes, regardless of success.
* Be sure to call [super suboperationFinished:operation].
*/
- (void)suboperationFinished:(MXCheckedOperation *)operation;
/**
* Subclasses can implement to perform an action once any suboperations have finished
*/
- (BOOL)endError:(NSError **)error;
/**
* Subclasses can override to customise the error presented when one or more suboperations fail
*/
- (NSError *)errorForSuberrors:(NSArray *)suboperationErrors;
/**
* Called at the end of the operation, even if the operation has ended as the result of an error.
* Subclasses can implement to clean up. Be sure to call [super finish].
*/
- (void)finish;
@end
//
// MXSuperOperation.m
// Mixim
//
// Created by Joe Rickerby on 10/04/2014.
// Copyright (c) 2014 Mixim Technology Ltd. All rights reserved.
//
#import "MXSuperOperation.h"
#import "NSOperation+MXOperationProgress.h"
@interface MXSuperOperation ()
{
NSMutableArray *_suboperations;
}
@property (strong) NSOperationQueue *queue;
- (void)suboperationsFinished;
@property (assign) BOOL isExecuting;
@property (assign) BOOL isFinished;
@property (strong) NSMutableArray *finishedSuboperations;
@end
@implementation MXSuperOperation
- (id)init
{
self = [super init];
if (self) {
self.queue = [NSOperationQueue new];
self.queue.maxConcurrentOperationCount = [[NSUserDefaults standardUserDefaults] boolForKey:@"unitask"] ? 1 : 4;
self.queue.suspended = YES;
self.queue.name = [NSString stringWithFormat:@"%@ operation queue", self];
_suboperations = [NSMutableArray new];
_finishedSuboperations = [NSMutableArray new];
}
return self;
}
#pragma mark MXSuperOperation
- (BOOL)startError:(NSError *__autoreleasing *)error
{
[NSException raise:NSGenericException format:@"Subclasses of %@ should override %s",
[MXCheckedOperation class], __PRETTY_FUNCTION__];
return NO;
}
- (void)addSuboperation:(MXCheckedOperation *)operation
{
__typeof__(self) __weak weakSelf = self;
[operation setCompletionBlockWithSuccess:^(id operation) {
[weakSelf suboperationComplete:operation];
[weakSelf suboperationFinished:operation];
} failure:^(id operation, NSError *error) {
[weakSelf suboperation:operation failedWithError:error];
[weakSelf suboperationFinished:operation];
}];
@synchronized(self.suboperations) {
[_suboperations addObject:operation];
}
[self.queue addOperation:operation];
}
- (void)suboperationComplete:(MXCheckedOperation *)operation
{
return;
}
- (void)suboperation:(MXCheckedOperation *)operation failedWithError:(NSError *)error
{
return;
}
- (void)suboperationFinished:(MXCheckedOperation *)operation
{
@synchronized(self) {
[self.finishedSuboperations addObject:operation];
if (self.finishedSuboperations.count == self.suboperations.count) {
[self suboperationsFinished];
}
}
}
- (BOOL)endError:(NSError *__autoreleasing *)error
{
return YES;
}
- (BOOL)suboperationsSuccess
{
NSArray *successArray;
@synchronized(self.suboperations) {
successArray = [self.suboperations valueForKey:@"success"];
}
return ![successArray containsObject:@( NO )];
}
- (NSArray *)suboperationErrors
{
NSArray *errorArray;
@synchronized(self.suboperations) {
errorArray = [self.suboperations valueForKey:@"error"];
}
return [errorArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"SELF != nil"]];
}
- (NSError *)errorForSuberrors:(NSArray *)suboperationErrors
{
return [NSError errorWithDomain:@"MXSuperOperationErrorDomain"
code:'subo'
userInfo:@{ @"operation": [self description],
@"suberrors": suboperationErrors }];
}
- (void)finish
{
if (self.success) {
NSParameterAssert(self.error == nil);
} else {
NSParameterAssert(self.error);
}
self.isExecuting = NO;
self.isFinished = YES;
}
#pragma mark NSOperation
- (void)start
{
NSError *error = nil;
self.isExecuting = YES;
self.success = [self startError:&error];
if (!self.success) {
self.error = error;
[self finish];
return;
}
if ([self.suboperations count] == 0) {
[self suboperationsFinished];
} else {
self.queue.suspended = NO;
}
}
- (BOOL)isConcurrent
{
return YES;
}
#pragma mark KVO
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
if ([@[@"isExecuting", @"isFinished"] containsObject:key]) {
return YES;
}
return [super automaticallyNotifiesObserversForKey:key];
}
#pragma mark Progress
- (long long)progressBytes
{
long long result = 0;
@synchronized(self.suboperations) {
for (NSOperation *operation in self.suboperations) {
result += operation.progressBytes;
}
}
return result;
}
- (long long)progressBytesMax
{
long long result = 0;
@synchronized(self.suboperations) {
for (NSOperation *operation in self.suboperations) {
result += operation.progressBytesMax;
}
}
return result;
}
- (long long)compressionProgressBytes
{
long long result = 0;
@synchronized(self.suboperations) {
for (NSOperation *operation in self.suboperations) {
result += operation.compressionProgressBytes;
}
}
return result;
}
#pragma mark Private
- (void)suboperationsFinished
{
if (self.suboperationsSuccess) {
NSError *error = nil;
self.success = [self endError:&error];
self.error = error;
} else {
NSParameterAssert(self.suboperationErrors.count > 0);
self.success = NO;
self.error = [self errorForSuberrors:self.suboperationErrors];
}
[self finish];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment