diff --git a/Bolts/Common/BFTask.h b/Bolts/Common/BFTask.h index 2ac84d601..27006948e 100644 --- a/Bolts/Common/BFTask.h +++ b/Bolts/Common/BFTask.h @@ -55,6 +55,14 @@ typedef id(^BFContinuationBlock)(BFTask *task); */ + (instancetype)taskForCompletionOfAllTasks:(NSArray *)tasks; +/*! + Returns a task that will be completed once all of the input tasks have completed. + If all tasks complete successfully without being faulted or cancelled the result will be + an `NSArray` of all task results in the order they were provided. + @param tasks An `NSArray` of the tasks to use as an input. + */ ++ (instancetype)taskForCompletionOfAllTasksWithResults:(NSArray *)tasks; + /*! Returns a task that will be completed a certain amount of time in the future. @param millis The approximate number of milliseconds to wait before the @@ -98,6 +106,11 @@ typedef id(^BFContinuationBlock)(BFTask *task); */ @property (nonatomic, assign, readonly, getter = isCancelled) BOOL cancelled; +/*! + Whether this task has completed due to an error or exception. + */ +@property (nonatomic, assign, readonly, getter = isFaulted) BOOL faulted; + /*! Whether this task has completed. */ diff --git a/Bolts/Common/BFTask.m b/Bolts/Common/BFTask.m index 157f06255..7ef710cde 100644 --- a/Bolts/Common/BFTask.m +++ b/Bolts/Common/BFTask.m @@ -26,6 +26,7 @@ @interface BFTask () { } @property (atomic, assign, readwrite, getter = isCancelled) BOOL cancelled; +@property (atomic, assign, readwrite, getter = isFaulted) BOOL faulted; @property (atomic, assign, readwrite, getter = isCompleted) BOOL completed; @property (nonatomic, retain, readwrite) NSObject *lock; @@ -131,6 +132,12 @@ + (instancetype)taskForCompletionOfAllTasks:(NSArray *)tasks { return tcs.task; } ++ (instancetype)taskForCompletionOfAllTasksWithResults:(NSArray *)tasks { + return [[self taskForCompletionOfAllTasks:tasks] continueWithSuccessBlock:^id(BFTask *task) { + return [tasks valueForKey:@"result"]; + }]; +} + + (instancetype)taskWithDelay:(int)millis { BFTaskCompletionSource *tcs = [BFTaskCompletionSource taskCompletionSource]; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, millis * NSEC_PER_MSEC); @@ -191,6 +198,7 @@ - (BOOL)trySetError:(NSError *)error { return NO; } self.completed = YES; + self.faulted = YES; _error = error; [self runContinuations]; return YES; @@ -216,6 +224,7 @@ - (BOOL)trySetException:(NSException *)exception { return NO; } self.completed = YES; + self.faulted = YES; _exception = exception; [self runContinuations]; return YES; @@ -228,6 +237,12 @@ - (BOOL)isCancelled { } } +- (BOOL)isFaulted { + @synchronized (self.lock) { + return _faulted; + } +} + - (void)cancel { @synchronized (self.lock) { if (![self trySetCancelled]) { diff --git a/BoltsTests/TaskTests.m b/BoltsTests/TaskTests.m index 15b0e9387..c266a052b 100644 --- a/BoltsTests/TaskTests.m +++ b/BoltsTests/TaskTests.m @@ -433,6 +433,50 @@ - (void)testTaskForCompletionOfAllTasksCancelled { }] waitUntilFinished]; } +- (void)testTaskForCompletionOfAllTasksNoTasksImmediateCompletion { + NSMutableArray *tasks = [NSMutableArray array]; + + BFTask *task = [BFTask taskForCompletionOfAllTasks:tasks]; + XCTAssertTrue(task.completed); + XCTAssertFalse(task.cancelled); + XCTAssertFalse(task.faulted); +} + +- (void)testTaskForCompletionOfAllTasksWithResultsSuccess { + NSMutableArray *tasks = [NSMutableArray array]; + + const int kTaskCount = 20; + for (int i = 0; i < kTaskCount; ++i) { + double sleepTimeInMs = i * 10; + int result = i + 1; + [tasks addObject:[[BFTask taskWithDelay:sleepTimeInMs] continueWithBlock:^id(BFTask *task) { + return @(result); + }]]; + } + + [[[BFTask taskForCompletionOfAllTasksWithResults:tasks] continueWithBlock:^id(BFTask *task) { + XCTAssertFalse(task.cancelled); + XCTAssertFalse(task.faulted); + + NSArray *results = task.result; + for (int i = 0; i < kTaskCount; ++i) { + NSNumber *individualResult = [results objectAtIndex:i]; + XCTAssertEqual([individualResult intValue], [((BFTask *)[tasks objectAtIndex:i]).result intValue]); + } + return nil; + }] waitUntilFinished]; +} + +- (void)testTaskForCompletionOfAllTasksWithResultsNoTasksImmediateCompletion { + NSMutableArray *tasks = [NSMutableArray array]; + + BFTask *task = [BFTask taskForCompletionOfAllTasksWithResults:tasks]; + XCTAssertTrue(task.completed); + XCTAssertFalse(task.cancelled); + XCTAssertFalse(task.faulted); + XCTAssertTrue(task.result != nil); +} + - (void)testWaitUntilFinished { BFTask *task = [[BFTask taskWithDelay:50] continueWithBlock:^id(BFTask *task) { return @"foo";