mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			251 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Matlab
		
	
		
		
			
		
	
	
			251 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Matlab
		
	
| 
											5 years ago
										 | // | ||
|  | //  Copyright (c) 2019 Open Whisper Systems. All rights reserved. | ||
|  | // | ||
|  | 
 | ||
|  | #import "OWSOperation.h" | ||
|  | #import "OWSBackgroundTask.h" | ||
|  | #import "OWSError.h" | ||
|  | 
 | ||
|  | NS_ASSUME_NONNULL_BEGIN | ||
|  | 
 | ||
|  | NSString *const OWSOperationKeyIsExecuting = @"isExecuting"; | ||
|  | NSString *const OWSOperationKeyIsFinished = @"isFinished"; | ||
|  | 
 | ||
|  | @interface OWSOperation () | ||
|  | 
 | ||
|  | @property (nullable) NSError *failingError; | ||
|  | @property (atomic) OWSOperationState operationState; | ||
|  | @property (nonatomic) OWSBackgroundTask *backgroundTask; | ||
|  | 
 | ||
|  | // This property should only be accessed on the main queue. | ||
|  | @property (nonatomic) NSTimer *_Nullable retryTimer; | ||
|  | 
 | ||
|  | @end | ||
|  | 
 | ||
|  | @implementation OWSOperation | ||
|  | 
 | ||
|  | - (instancetype)init | ||
|  | { | ||
|  |     self = [super init]; | ||
|  |     if (!self) { | ||
|  |         return self; | ||
|  |     } | ||
|  | 
 | ||
|  |     _operationState = OWSOperationStateNew; | ||
|  |     _backgroundTask = [OWSBackgroundTask backgroundTaskWithLabel:self.logTag]; | ||
|  | 
 | ||
|  |     // Operations are not retryable by default. | ||
|  |     _remainingRetries = 0; | ||
|  | 
 | ||
|  |     return self; | ||
|  | } | ||
|  | 
 | ||
|  | - (void)dealloc | ||
|  | { | ||
|  |     OWSLogDebug(@"in dealloc"); | ||
|  | } | ||
|  | 
 | ||
|  | #pragma mark - Subclass Overrides | ||
|  | 
 | ||
|  | // Called one time only | ||
|  | - (nullable NSError *)checkForPreconditionError | ||
|  | { | ||
|  |     // OWSOperation have a notion of failure, which is inferred by the presence of a `failingError`. | ||
|  |     // | ||
|  |     // By default, any failing dependency cascades that failure to it's dependent. | ||
|  |     // If you'd like different behavior, override this method (`checkForPreconditionError`) without calling `super`. | ||
|  |     for (NSOperation *dependency in self.dependencies) { | ||
|  |         if (![dependency isKindOfClass:[OWSOperation class]]) { | ||
|  |             // Native operations, like NSOperation and NSBlockOperation have no notion of "failure". | ||
|  |             // So there's no `failingError` to cascade. | ||
|  |             continue; | ||
|  |         } | ||
|  | 
 | ||
|  |         OWSOperation *dependentOperation = (OWSOperation *)dependency; | ||
|  | 
 | ||
|  |         // Don't proceed if dependency failed - surface the dependency's error. | ||
|  |         NSError *_Nullable dependencyError = dependentOperation.failingError; | ||
|  |         if (dependencyError != nil) { | ||
|  |             return dependencyError; | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     return nil; | ||
|  | } | ||
|  | 
 | ||
|  | // Called every retry, this is where the bulk of the operation's work should go. | ||
|  | - (void)run | ||
|  | { | ||
|  |     OWSAbstractMethod(); | ||
|  | } | ||
|  | 
 | ||
|  | // Called at most one time. | ||
|  | - (void)didSucceed | ||
|  | { | ||
|  |     // no-op | ||
|  |     // Override in subclass if necessary | ||
|  | } | ||
|  | 
 | ||
|  | // Called at most one time. | ||
|  | - (void)didCancel | ||
|  | { | ||
|  |     // no-op | ||
|  |     // Override in subclass if necessary | ||
|  | } | ||
|  | 
 | ||
|  | // Called zero or more times, retry may be possible | ||
|  | - (void)didReportError:(NSError *)error | ||
|  | { | ||
|  |     // no-op | ||
|  |     // Override in subclass if necessary | ||
|  | } | ||
|  | 
 | ||
|  | // Called at most one time, once retry is no longer possible. | ||
|  | - (void)didFailWithError:(NSError *)error | ||
|  | { | ||
|  |     // no-op | ||
|  |     // Override in subclass if necessary | ||
|  | } | ||
|  | 
 | ||
|  | #pragma mark - NSOperation overrides | ||
|  | 
 | ||
|  | // Do not override this method in a subclass instead, override `run` | ||
|  | - (void)main | ||
|  | { | ||
|  |     OWSLogDebug(@"started."); | ||
|  |     NSError *_Nullable preconditionError = [self checkForPreconditionError]; | ||
|  |     if (preconditionError) { | ||
|  |         [self failOperationWithError:preconditionError]; | ||
|  |         return; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (self.isCancelled) { | ||
|  |         [self reportCancelled]; | ||
|  |         return; | ||
|  |     } | ||
|  |      | ||
|  |     [self run]; | ||
|  | } | ||
|  | 
 | ||
|  | - (void)runAnyQueuedRetry | ||
|  | { | ||
|  |     dispatch_async(dispatch_get_main_queue(), ^{ | ||
|  |         NSTimer *_Nullable retryTimer = self.retryTimer; | ||
|  |         self.retryTimer = nil; | ||
|  |         [retryTimer invalidate]; | ||
|  | 
 | ||
|  |         if (retryTimer != nil) { | ||
|  |             dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ | ||
|  |                 [self run]; | ||
|  |             }); | ||
|  |         } else { | ||
|  |             OWSLogVerbose(@"not re-running since operation is already running."); | ||
|  |         } | ||
|  |     }); | ||
|  | } | ||
|  | 
 | ||
|  | #pragma mark - Public Methods | ||
|  | 
 | ||
|  | // These methods are not intended to be subclassed | ||
|  | - (void)reportSuccess | ||
|  | { | ||
|  |     OWSLogDebug(@"succeeded."); | ||
|  |     [self didSucceed]; | ||
|  |     [self markAsComplete]; | ||
|  | } | ||
|  | 
 | ||
|  | // These methods are not intended to be subclassed | ||
|  | - (void)reportCancelled | ||
|  | { | ||
|  |     OWSLogDebug(@"cancelled."); | ||
|  |     [self didCancel]; | ||
|  |     [self markAsComplete]; | ||
|  | } | ||
|  | 
 | ||
|  | - (void)reportError:(NSError *)error | ||
|  | { | ||
|  |     [self didReportError:error]; | ||
|  | 
 | ||
|  |     if (self.remainingRetries == 0) { | ||
|  |         [self failOperationWithError:error]; | ||
|  |         return; | ||
|  |     } | ||
|  | 
 | ||
|  |     self.remainingRetries--; | ||
|  | 
 | ||
|  |     dispatch_async(dispatch_get_main_queue(), ^{ | ||
|  |         OWSAssertDebug(self.retryTimer == nil); | ||
|  |         [self.retryTimer invalidate]; | ||
|  | 
 | ||
|  |         // The `scheduledTimerWith*` methods add the timer to the current thread's RunLoop. | ||
|  |         // Since Operations typically run on a background thread, that would mean the background | ||
|  |         // thread's RunLoop. However, the OS can spin down background threads if there's no work | ||
|  |         // being done, so we run the risk of the timer's RunLoop being deallocated before it's | ||
|  |         // fired. | ||
|  |         // | ||
|  |         // To ensure the timer's thread sticks around, we schedule it while on the main RunLoop. | ||
|  |         self.retryTimer = [NSTimer weakScheduledTimerWithTimeInterval:self.retryInterval | ||
|  |                                                                target:self | ||
|  |                                                              selector:@selector(runAnyQueuedRetry) | ||
|  |                                                              userInfo:nil | ||
|  |                                                               repeats:NO]; | ||
|  |     }); | ||
|  | } | ||
|  | 
 | ||
|  | // Override in subclass if you want something more sophisticated, e.g. exponential backoff | ||
|  | - (NSTimeInterval)retryInterval | ||
|  | { | ||
|  |     return 0.1; | ||
|  | } | ||
|  | 
 | ||
|  | #pragma mark - Life Cycle | ||
|  | 
 | ||
|  | - (void)failOperationWithError:(NSError *)error | ||
|  | { | ||
|  |     OWSLogDebug(@"failed terminally."); | ||
|  |     self.failingError = error; | ||
|  | 
 | ||
|  |     [self didFailWithError:error]; | ||
|  |     [self markAsComplete]; | ||
|  | } | ||
|  | 
 | ||
|  | - (BOOL)isExecuting | ||
|  | { | ||
|  |     return self.operationState == OWSOperationStateExecuting; | ||
|  | } | ||
|  | 
 | ||
|  | - (BOOL)isFinished | ||
|  | { | ||
|  |     return self.operationState == OWSOperationStateFinished; | ||
|  | } | ||
|  | 
 | ||
|  | - (void)start | ||
|  | { | ||
|  |     [self willChangeValueForKey:OWSOperationKeyIsExecuting]; | ||
|  |     self.operationState = OWSOperationStateExecuting; | ||
|  |     [self didChangeValueForKey:OWSOperationKeyIsExecuting]; | ||
|  | 
 | ||
|  |     [self main]; | ||
|  | } | ||
|  | 
 | ||
|  | - (void)markAsComplete | ||
|  | { | ||
|  |     [self willChangeValueForKey:OWSOperationKeyIsExecuting]; | ||
|  |     [self willChangeValueForKey:OWSOperationKeyIsFinished]; | ||
|  | 
 | ||
|  |     // Ensure we call the success or failure handler exactly once. | ||
|  |     @synchronized(self) | ||
|  |     { | ||
|  |         OWSAssertDebug(self.operationState != OWSOperationStateFinished); | ||
|  | 
 | ||
|  |         self.operationState = OWSOperationStateFinished; | ||
|  |     } | ||
|  | 
 | ||
|  |     [self didChangeValueForKey:OWSOperationKeyIsExecuting]; | ||
|  |     [self didChangeValueForKey:OWSOperationKeyIsFinished]; | ||
|  | } | ||
|  | 
 | ||
|  | @end | ||
|  | 
 | ||
|  | NS_ASSUME_NONNULL_END |