Last active
August 29, 2015 14:17
MXSuperOperation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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