123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539 |
- /*
- * Copyright (c) 2014, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- *
- */
- #import "OSSTask.h"
- #import "OSSLog.h"
- #import <libkern/OSAtomic.h>
- #import "OSSBolts.h"
- NS_ASSUME_NONNULL_BEGIN
- __attribute__ ((noinline)) void ossWarnBlockingOperationOnMainThread() {
- NSLog(@"Warning: A long-running operation is being executed on the main thread. \n"
- " Break on warnBlockingOperationOnMainThread() to debug.");
- }
- NSString *const OSSTaskErrorDomain = @"bolts";
- NSInteger const kOSSMultipleErrorsError = 80175001;
- NSString *const OSSTaskMultipleExceptionsException = @"OSSMultipleExceptionsException";
- NSString *const OSSTaskMultipleErrorsUserInfoKey = @"errors";
- NSString *const OSSTaskMultipleExceptionsUserInfoKey = @"exceptions";
- @interface OSSTask () {
- id _result;
- NSError *_error;
- NSException *_exception;
- }
- @property (nonatomic, assign, readwrite, getter=isCancelled) BOOL cancelled;
- @property (nonatomic, assign, readwrite, getter=isFaulted) BOOL faulted;
- @property (nonatomic, assign, readwrite, getter=isCompleted) BOOL completed;
- @property (nonatomic, strong) NSObject *lock;
- @property (nonatomic, strong) NSCondition *condition;
- @property (nonatomic, strong) NSMutableArray *callbacks;
- @end
- @implementation OSSTask
- #pragma mark - Initializer
- - (instancetype)init {
- self = [super init];
- if (!self) return self;
- _lock = [[NSObject alloc] init];
- _condition = [[NSCondition alloc] init];
- _callbacks = [NSMutableArray array];
- return self;
- }
- - (instancetype)initWithResult:(_Nullable id)result {
- self = [super init];
- if (self) {
- [self trySetResult:result];
- }
- return self;
- }
- - (instancetype)initWithError:(NSError *)error {
- self = [super init];
- if (!self) return self;
- [self trySetError:error];
- return self;
- }
- - (instancetype)initWithException:(NSException *)exception {
- self = [super init];
- if (!self) return self;
- [self trySetException:exception];
- return self;
- }
- - (instancetype)initCancelled {
- self = [super init];
- if (!self) return self;
- [self trySetCancelled];
- return self;
- }
- #pragma mark - Task Class methods
- + (instancetype)taskWithResult:(_Nullable id)result {
- return [[self alloc] initWithResult:result];
- }
- + (instancetype)taskWithError:(NSError *)error {
- return [[self alloc] initWithError:error];
- }
- + (instancetype)taskWithException:(NSException *)exception {
- return [[self alloc] initWithException:exception];
- }
- + (instancetype)cancelledTask {
- return [[self alloc] initCancelled];
- }
- + (instancetype)taskForCompletionOfAllTasks:(nullable NSArray<OSSTask *> *)tasks {
- __block int32_t total = (int32_t)tasks.count;
- if (total == 0) {
- return [self taskWithResult:nil];
- }
- __block int32_t cancelled = 0;
- NSObject *lock = [[NSObject alloc] init];
- NSMutableArray *errors = [NSMutableArray array];
- NSMutableArray *exceptions = [NSMutableArray array];
- OSSTaskCompletionSource *tcs = [OSSTaskCompletionSource taskCompletionSource];
- for (OSSTask *task in tasks) {
- [task continueWithBlock:^id(OSSTask *task) {
- if (task.exception) {
- @synchronized (lock) {
- [exceptions addObject:task.exception];
- }
- } else if (task.error) {
- @synchronized (lock) {
- [errors addObject:task.error];
- }
- } else if (task.cancelled) {
- OSAtomicIncrement32Barrier(&cancelled);
- }
- if (OSAtomicDecrement32Barrier(&total) == 0) {
- if (exceptions.count > 0) {
- if (exceptions.count == 1) {
- tcs.exception = [exceptions firstObject];
- } else {
- NSException *exception =
- [NSException exceptionWithName:OSSTaskMultipleExceptionsException
- reason:@"There were multiple exceptions."
- userInfo:@{ OSSTaskMultipleExceptionsUserInfoKey: exceptions }];
- tcs.exception = exception;
- }
- } else if (errors.count > 0) {
- if (errors.count == 1) {
- tcs.error = [errors firstObject];
- } else {
- NSError *error = [NSError errorWithDomain:OSSTaskErrorDomain
- code:kOSSMultipleErrorsError
- userInfo:@{ OSSTaskMultipleErrorsUserInfoKey: errors }];
- tcs.error = error;
- }
- } else if (cancelled > 0) {
- [tcs cancel];
- } else {
- tcs.result = nil;
- }
- }
- return nil;
- }];
- }
- return tcs.task;
- }
- + (instancetype)taskForCompletionOfAllTasksWithResults:(nullable NSArray<OSSTask *> *)tasks {
- return [[self taskForCompletionOfAllTasks:tasks] continueWithSuccessBlock:^id(OSSTask *task) {
- return [tasks valueForKey:@"result"];
- }];
- }
- + (instancetype)taskForCompletionOfAnyTask:(nullable NSArray<OSSTask *> *)tasks
- {
- __block int32_t total = (int32_t)tasks.count;
- if (total == 0) {
- return [self taskWithResult:nil];
- }
-
- __block int completed = 0;
- __block int32_t cancelled = 0;
-
- NSObject *lock = [NSObject new];
- NSMutableArray<NSError *> *errors = [NSMutableArray new];
- NSMutableArray<NSException *> *exceptions = [NSMutableArray new];
-
- OSSTaskCompletionSource *source = [OSSTaskCompletionSource taskCompletionSource];
- for (OSSTask *task in tasks) {
- [task continueWithBlock:^id(OSSTask *task) {
- if (task.exception != nil) {
- @synchronized(lock) {
- [exceptions addObject:task.exception];
- }
- } else if (task.error != nil) {
- @synchronized(lock) {
- [errors addObject:task.error];
- }
- } else if (task.cancelled) {
- OSAtomicIncrement32Barrier(&cancelled);
- } else {
- if(OSAtomicCompareAndSwap32Barrier(0, 1, &completed)) {
- [source setResult:task.result];
- }
- }
-
- if (OSAtomicDecrement32Barrier(&total) == 0 &&
- OSAtomicCompareAndSwap32Barrier(0, 1, &completed)) {
- if (cancelled > 0) {
- [source cancel];
- } else if (exceptions.count > 0) {
- if (exceptions.count == 1) {
- source.exception = exceptions.firstObject;
- } else {
- NSException *exception =
- [NSException exceptionWithName:OSSTaskMultipleExceptionsException
- reason:@"There were multiple exceptions."
- userInfo:@{ @"exceptions": exceptions }];
- source.exception = exception;
- }
- } else if (errors.count > 0) {
- if (errors.count == 1) {
- source.error = errors.firstObject;
- } else {
- NSError *error = [NSError errorWithDomain:OSSTaskErrorDomain
- code:kOSSMultipleErrorsError
- userInfo:@{ @"errors": errors }];
- source.error = error;
- }
- }
- }
- // Abort execution of per tasks continuations
- return nil;
- }];
- }
- return source.task;
- }
- + (instancetype)taskWithDelay:(int)millis {
- OSSTaskCompletionSource *tcs = [OSSTaskCompletionSource taskCompletionSource];
- dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, millis * NSEC_PER_MSEC);
- dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
- tcs.result = nil;
- });
- return tcs.task;
- }
- + (instancetype)taskWithDelay:(int)millis cancellationToken:(nullable OSSCancellationToken *)token {
- if (token.cancellationRequested) {
- return [OSSTask cancelledTask];
- }
- OSSTaskCompletionSource *tcs = [OSSTaskCompletionSource taskCompletionSource];
- dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, millis * NSEC_PER_MSEC);
- dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
- if (token.cancellationRequested) {
- [tcs cancel];
- return;
- }
- tcs.result = nil;
- });
- return tcs.task;
- }
- + (instancetype)taskFromExecutor:(OSSExecutor *)executor withBlock:(nullable id (^)(void))block {
- return [[self taskWithResult:nil] continueWithExecutor:executor withBlock:^id(OSSTask *task) {
- return block();
- }];
- }
- #pragma mark - Custom Setters/Getters
- - (nullable id)result {
- @synchronized(self.lock) {
- return _result;
- }
- }
- - (BOOL)trySetResult:(nullable id)result {
- @synchronized(self.lock) {
- if (self.completed) {
- return NO;
- }
- self.completed = YES;
- _result = result;
- [self runContinuations];
- return YES;
- }
- }
- - (nullable NSError *)error {
- @synchronized(self.lock) {
- return _error;
- }
- }
- - (BOOL)trySetError:(NSError *)error {
- @synchronized(self.lock) {
- if (self.completed) {
- return NO;
- }
- self.completed = YES;
- self.faulted = YES;
- _error = error;
- [self runContinuations];
- return YES;
- }
- }
- - (nullable NSException *)exception {
- @synchronized(self.lock) {
- return _exception;
- }
- }
- - (BOOL)trySetException:(NSException *)exception {
- @synchronized(self.lock) {
- if (self.completed) {
- return NO;
- }
- self.completed = YES;
- self.faulted = YES;
- _exception = exception;
- [self runContinuations];
- return YES;
- }
- }
- - (BOOL)isCancelled {
- @synchronized(self.lock) {
- return _cancelled;
- }
- }
- - (BOOL)isFaulted {
- @synchronized(self.lock) {
- return _faulted;
- }
- }
- - (BOOL)trySetCancelled {
- @synchronized(self.lock) {
- if (self.completed) {
- return NO;
- }
- self.completed = YES;
- self.cancelled = YES;
- [self runContinuations];
- return YES;
- }
- }
- - (BOOL)isCompleted {
- @synchronized(self.lock) {
- return _completed;
- }
- }
- - (void)runContinuations {
- @synchronized(self.lock) {
- [self.condition lock];
- [self.condition broadcast];
- [self.condition unlock];
- for (void (^callback)(void) in self.callbacks) {
- callback();
- }
- [self.callbacks removeAllObjects];
- }
- }
- #pragma mark - Chaining methods
- - (OSSTask *)continueWithExecutor:(OSSExecutor *)executor withBlock:(OSSContinuationBlock)block {
- return [self continueWithExecutor:executor block:block cancellationToken:nil];
- }
- - (OSSTask *)continueWithExecutor:(OSSExecutor *)executor
- block:(OSSContinuationBlock)block
- cancellationToken:(nullable OSSCancellationToken *)cancellationToken {
- OSSTaskCompletionSource *tcs = [OSSTaskCompletionSource taskCompletionSource];
- // Capture all of the state that needs to used when the continuation is complete.
- dispatch_block_t executionBlock = ^{
- if (cancellationToken.cancellationRequested) {
- [tcs cancel];
- return;
- }
- id result = nil;
- @try {
- result = block(self);
- } @catch (NSException *exception) {
- tcs.exception = exception;
- OSSLogError(@"exception name: %@",[exception name]);
- OSSLogError(@"exception reason: %@",[exception reason]);
- return;
- }
- if ([result isKindOfClass:[OSSTask class]]) {
- id (^setupWithTask) (OSSTask *) = ^id(OSSTask *task) {
- if (cancellationToken.cancellationRequested || task.cancelled) {
- [tcs cancel];
- } else if (task.exception) {
- tcs.exception = task.exception;
- } else if (task.error) {
- tcs.error = task.error;
- } else {
- tcs.result = task.result;
- }
- return nil;
- };
- OSSTask *resultTask = (OSSTask *)result;
- if (resultTask.completed) {
- setupWithTask(resultTask);
- } else {
- [resultTask continueWithBlock:setupWithTask];
- }
- } else {
- tcs.result = result;
- }
- };
- BOOL completed;
- @synchronized(self.lock) {
- completed = self.completed;
- if (!completed) {
- [self.callbacks addObject:[^{
- [executor execute:executionBlock];
- } copy]];
- }
- }
- if (completed) {
- [executor execute:executionBlock];
- }
- return tcs.task;
- }
- - (OSSTask *)continueWithBlock:(OSSContinuationBlock)block {
- return [self continueWithExecutor:[OSSExecutor defaultExecutor] block:block cancellationToken:nil];
- }
- - (OSSTask *)continueWithBlock:(OSSContinuationBlock)block cancellationToken:(nullable OSSCancellationToken *)cancellationToken {
- return [self continueWithExecutor:[OSSExecutor defaultExecutor] block:block cancellationToken:cancellationToken];
- }
- - (OSSTask *)continueWithExecutor:(OSSExecutor *)executor
- withSuccessBlock:(OSSContinuationBlock)block {
- return [self continueWithExecutor:executor successBlock:block cancellationToken:nil];
- }
- - (OSSTask *)continueWithExecutor:(OSSExecutor *)executor
- successBlock:(OSSContinuationBlock)block
- cancellationToken:(nullable OSSCancellationToken *)cancellationToken {
- if (cancellationToken.cancellationRequested) {
- return [OSSTask cancelledTask];
- }
- return [self continueWithExecutor:executor block:^id(OSSTask *task) {
- if (task.faulted || task.cancelled) {
- return task;
- } else {
- return block(task);
- }
- } cancellationToken:cancellationToken];
- }
- - (OSSTask *)continueWithSuccessBlock:(OSSContinuationBlock)block {
- return [self continueWithExecutor:[OSSExecutor defaultExecutor] successBlock:block cancellationToken:nil];
- }
- - (OSSTask *)continueWithSuccessBlock:(OSSContinuationBlock)block cancellationToken:(nullable OSSCancellationToken *)cancellationToken {
- return [self continueWithExecutor:[OSSExecutor defaultExecutor] successBlock:block cancellationToken:cancellationToken];
- }
- #pragma mark - Syncing Task (Avoid it)
- - (void)warnOperationOnMainThread {
- ossWarnBlockingOperationOnMainThread();
- }
- - (void)waitUntilFinished {
- if ([NSThread isMainThread]) {
- [self warnOperationOnMainThread];
- }
- @synchronized(self.lock) {
- if (self.completed) {
- return;
- }
- [self.condition lock];
- }
- while (!self.completed) {
- [self.condition wait];
- }
- [self.condition unlock];
- }
- #pragma mark - NSObject
- - (NSString *)description {
- // Acquire the data from the locked properties
- BOOL completed;
- BOOL cancelled;
- BOOL faulted;
- NSString *resultDescription = nil;
- @synchronized(self.lock) {
- completed = self.completed;
- cancelled = self.cancelled;
- faulted = self.faulted;
- resultDescription = completed ? [NSString stringWithFormat:@" result = %@", self.result] : @"";
- }
- // Description string includes status information and, if available, the
- // result since in some ways this is what a promise actually "is".
- return [NSString stringWithFormat:@"<%@: %p; completed = %@; cancelled = %@; faulted = %@;%@>",
- NSStringFromClass([self class]),
- self,
- completed ? @"YES" : @"NO",
- cancelled ? @"YES" : @"NO",
- faulted ? @"YES" : @"NO",
- resultDescription];
- }
- @end
- NS_ASSUME_NONNULL_END
|