Last active
November 19, 2020 16:46
-
-
Save fareast555/2b456b8484f19fff71d01d25322174ec to your computer and use it in GitHub Desktop.
A sub-classable NSOperation subclass useful for NSOperationQueues connected by adapter blocks, followed by a convenience NSOperationQueue category to wire your custom MPC_NSOperation and NSBlockOperations together.
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
// | |
// MPC_NSOperation.h | |
// | |
// Created by Michael Critchley on 2017/11/14. | |
// Copyright © 2017 Michael Critchley. All rights reserved. | |
//***************** | |
//This is the parent class for all MPC_NSOperation objects. | |
//To use: | |
//1. Subclass this method | |
//2. Implement the -(void)start {} method | |
//3. Subclasses must call if (![super initializeExecution]) return; at the top of the start method | |
//This is because the parent class checks and cancels execution if previous MPC_NSOperation op was cancelled | |
//4. **IMPORTANT** | |
//Subclasses must call [super completeOperation] when the operation work is done so queue can be removed | |
//Even if you call [myOp cancel], you mus still call [super completeOperation] somewhere in the executing code | |
//or you will experience more blockage than eating a pound of fiber. | |
//5. **IMPORTANT** | |
//If using blockOperationWithBlock^() as an intermediate step between two MPC_NSOperations, | |
//you should forward error objects, and you must manually check if the previous block | |
//was cancelled, and if so, cancel the next in the queue. For | |
//example, if between MPC_NSOperation1 and MPC_NSOperation2 we put a block... | |
// NSBlockOperation *blockOp = [NSBlockOperation blockOperationWithBlock:^{ | |
// if (MPC_Operation1.isCancelled) { | |
// [MPC_Operation2 cancel]; | |
// } | |
// MPC_Operation2.error = MPC_Operation1.error; | |
// ... | |
// } | |
// [blockOp addDependency:MPC_Operation1]; | |
// [MPC_Operation2 addDependency:blockOp]; | |
// | |
// | |
//6. Convenience method available for creating depency chains of ops | |
// Import the "NSOperationQueue+MPC_NSOperationQueue.h" file | |
// Package IN ORDER OF EXECUTION all MPC_NSOperation instances or blockOperations in an NSArray | |
// Then call the following method on your NSOperationQueue instance | |
// [myOpqueue addDependenciesAndDefaultAdapterBlocksBetweenMPC_NSOperationsArray:myOperationsArray]; | |
// This will 1) Add intermediate blocks between MPC_NSOperations to pass forward any errors | |
// 2) Set a chain of dependencies between all objects | |
// 3) Add all operations to the operationQueue instance that called the method, which begins execution | |
//7. Call to [self errorWithMessage:] to pass a custom error message if there is some unexpected break in variables being passed forward by the proceding class. The error creation and setting will be handled in the parent class. | |
//***************** | |
#import <Foundation/Foundation.h> | |
@import CloudKit; | |
@interface MPC_NSOperation : NSOperation | |
+ (instancetype)MPC_Operation; | |
- (BOOL)initializeExecution; | |
- (void)completeOperation; | |
//Flags used internally (.executing and .finished are readOnly) | |
@property (nonatomic, assign) BOOL nowExecuting; | |
//@property (atomic, assign) BOOL nowExecuting; | |
@property (atomic, assign) BOOL nowFinished; | |
//All MPC_NSOperations should present an error on error | |
@property (strong, atomic) NSError *error; | |
@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
// | |
// MPC_NSOperation.m | |
// | |
// Created by Michael Critchley on 2017/11/14. | |
// Copyright © 2017 Michael Critchley. All rights reserved. | |
#import "MPC_NSOperation.h" | |
@interface MPC_NSOperation () | |
@property (assign, nonatomic) BOOL isFinishedParentHolderProperty; | |
@property (assign, nonatomic) BOOL isExecutingHolderProperty; | |
@end | |
@implementation MPC_NSOperation | |
#pragma mark - Factory init | |
+ (instancetype)MPC_Operation | |
{ | |
return [[self alloc]init]; | |
} | |
- (instancetype)init | |
{ | |
self = [super init]; | |
return self; | |
} | |
#pragma mark - Implementations for subclasses | |
- (BOOL)initializeExecution //A required call from subclasses | |
{ | |
//1. Check if dependency[0] was cancelled | |
for (NSOperation *op in [self dependencies]) { | |
//2. If dependency was cancelled, also cancel this op | |
if (op.isCancelled) | |
[self cancel]; | |
} | |
//3. If we are cancelled, set this op as finished | |
if ([self isCancelled]) | |
{ | |
//4. Inform the op that we are going to change state | |
[self willChangeValueForKey:@"isFinished"]; | |
//5. Set the local holder flags | |
self.nowFinished = YES; | |
//6. Inform the op to call to get the current status from the official getters | |
[self didChangeValueForKey:@"isFinished"]; | |
//7. Inform the subclass to NOT continue with its workblock | |
return NO; | |
} | |
//8. If the operation is not canceled, inform the op we will change state. | |
[self willChangeValueForKey:@"isExecuting"]; | |
//9. Set the local holder flag status | |
self.nowExecuting = YES; | |
//10. Inform the op to call to get the current execution status from the official getters | |
[self didChangeValueForKey:@"isExecuting"]; | |
//11. Inform the subclass to continue with its workblock | |
return YES; | |
} | |
//Delegate getters | |
- (BOOL)isExecuting { | |
return self.nowExecuting; | |
} | |
- (BOOL)isFinished { | |
return self.nowFinished; | |
} | |
- (BOOL)isAsynchronous { | |
return YES; | |
} | |
//Setters that implement subclass property setting via self. | |
- (void)setNowFinished:(BOOL)nowFinished | |
{ | |
self.isFinishedParentHolderProperty = nowFinished; | |
} | |
- (BOOL)nowFinished | |
{ | |
return self.isFinishedParentHolderProperty; | |
} | |
- (void)setNowExecuting:(BOOL)nowExecuting | |
{ | |
self.isExecutingHolderProperty = nowExecuting; | |
} | |
- (BOOL)nowExecuting | |
{ | |
return self.isExecutingHolderProperty; | |
} | |
//Mark the subclassed operation as complete | |
- (void)completeOperation { | |
[self willChangeValueForKey:@"isFinished"]; | |
[self willChangeValueForKey:@"isExecuting"]; | |
self.nowExecuting = NO; | |
self.nowFinished = YES; | |
[self didChangeValueForKey:@"isExecuting"]; | |
[self didChangeValueForKey:@"isFinished"]; | |
} | |
#pragma mark - Error handling | |
- (void)exposeErrorWithMessage:(NSString *)message | |
{ | |
if (!message) | |
message = @""; | |
self.error = [[NSError alloc]initWithDomain:@"MPC_NSOperationDomain" | |
code:-1 | |
userInfo:@{NSLocalizedDescriptionKey : message, | |
NSLocalizedFailureReasonErrorKey : @"Likely issue with required parameters being passed as nil (or not at all)", | |
NSLocalizedRecoverySuggestionErrorKey : @"Use adapter blocks (blockOperationWithBlock^() between operations to set variables on upcoming operations. See MPC_NSOperation.h for more."}]; | |
} | |
@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
// | |
// NSOperationQueue+MPC_NSOperationQueue.h | |
// | |
// Created by Michael Critchley on 2017/11/15. | |
// Copyright © 2017 Michael Critchley. All rights reserved. | |
/***************** | |
This category has a convenience instance method for NSOperationQueue that packages | |
MPC_NSOperation subclasses and intermediate adapter blocks. Specificially, the method | |
1. Puts a small, intermediate block of code between two MPC_NSOperation subclasses | |
to carry forward errors and .isCancelled state. | |
2. Creates a depenency chain starting from array[0] down to array[n] | |
3. Adds the operations to the receiving NSOperationQueue object that called the method | |
** Do not use this with regular NSOperation objects. It will break. Use MPC_NSOperation objects only | |
** Add your ops to your array in the order they should be performed | |
** You can add blockOperationWithBlock^() operations, but in the code you write in the | |
block, be sure to pass forward the error and is.Cancelled state information to the | |
next op in the queue (See MPC_NSOperation.h) for an example | |
******************/ | |
#import <Foundation/Foundation.h> | |
@interface NSOperationQueue (MPC_NSOperationQueue) | |
//CREATES AND ADDS TO queue a dependency chain starting from array[0] (ie, array[0]will be the first op performed | |
//If array[i] and array[i+1] are both subclasses of MPC_NSOperation, it will add a default NSBlockOperation | |
//that passes errors forward. | |
- (void )addDependenciesAndDefaultAdapterBlocksBetweenMPC_NSOperationsArray:(NSArray *)operationsArray; | |
@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
// | |
// NSOperationQueue+MPC_NSOperationQueue.m | |
// | |
// Created by Michael Critchley on 2017/11/15. | |
// Copyright © 2017 Michael Critchley. All rights reserved. | |
// | |
#import "NSOperationQueue+MPC_NSOperationQueue.h" | |
#import "MPC_NSOperation.h" | |
@implementation NSOperationQueue (MPC_NSOperationQueue) | |
- (void)addDependenciesAndDefaultAdapterBlocksBetweenMPC_NSOperationsArray:(NSArray *)operationsArray | |
{ | |
if (!operationsArray) return; | |
if (operationsArray.count < 2) return; | |
//1. Add default blocks to pass forward errors / isCancelled | |
NSArray *arrayWithBlockOps = [NSOperationQueue _arrayWithAddedDefaultNSBlockOperationsFromOperationsArray:operationsArray]; | |
//2. Set dependencies | |
NSArray *arrayWithDependencies = [self configureDependenciesForNSOperationsArray:arrayWithBlockOps]; | |
//3. Add all of these ops to the operationQueue | |
[self addOperations:arrayWithDependencies waitUntilFinished:NO]; | |
} | |
+ (NSArray *)_arrayWithAddedDefaultNSBlockOperationsFromOperationsArray:(NSArray *)operationsArray | |
{ | |
//1. Create iVars | |
Class checkClass = [MPC_NSOperation class]; | |
NSMutableArray *holder = [NSMutableArray new]; | |
//2. Iterate through initial array to add small adapter blocks that will | |
//pass the error object and .isCancelled flag from Op1 (before block) over to Op2 (after block) | |
for (NSOperation *op in operationsArray) { | |
//3. Add each object from original array | |
[holder addObject:op]; | |
//4. End if we are on the final object | |
if ([op isEqual:operationsArray.lastObject]) | |
break; | |
//5. Check if both this object and the next are MPC_Operation subclasses | |
NSInteger index1 = [operationsArray indexOfObject:op]; | |
NSOperation *op2 = operationsArray[index1 + 1]; | |
if ([op isKindOfClass:checkClass] && | |
[op2 isKindOfClass:checkClass]) { | |
//6. If so, create a block that forwards the state variables of Op1 to Op2 | |
NSBlockOperation *passForward = [NSBlockOperation blockOperationWithBlock:^{ | |
//7. Cancel subsequent op if previous was in a cancelled state | |
if (op.isCancelled) | |
[op2 cancel]; | |
//8. Pass forward errors (nil or otherwise) | |
((MPC_NSOperation *)op2).error = ((MPC_NSOperation *)op).error; | |
}]; | |
//9. Add the block to the holder | |
[holder addObject:passForward]; | |
} | |
} | |
return [holder copy]; | |
} | |
- (NSArray *)configureDependenciesForNSOperationsArray:(NSArray *)operationsArray | |
{ | |
NSInteger i = 0; | |
for (NSOperation *op in operationsArray) { | |
//End if we are on the final object | |
if ([op isEqual:operationsArray.lastObject]) | |
break; | |
//Add a depency from the earlier op to the next one in the chain | |
NSInteger index1 = [operationsArray indexOfObject:op]; | |
NSOperation *op2 = operationsArray[index1 + 1]; | |
[op2 addDependency:op]; | |
i++; | |
} | |
return operationsArray; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment