Skip to content

Instantly share code, notes, and snippets.

@fareast555
Last active November 19, 2020 16:46
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fareast555/2b456b8484f19fff71d01d25322174ec to your computer and use it in GitHub Desktop.
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.
//
// 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
//
// 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
//
// 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
//
// 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