This commit is contained in:
启星
2025-08-12 14:27:12 +08:00
parent 9d18b353b1
commit 1bd5e77c45
8785 changed files with 978163 additions and 2 deletions

View File

@@ -0,0 +1,26 @@
/*
* 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 "OSSCancellationToken.h"
#import "OSSCancellationTokenRegistration.h"
#import "OSSCancellationTokenSource.h"
#import "OSSExecutor.h"
#import "OSSTask.h"
#import "OSSTaskCompletionSource.h"
NS_ASSUME_NONNULL_BEGIN
/**
A string containing the version of the Bolts Framework used by the current application.
*/
extern NSString *const OSSBoltsFrameworkVersionString;
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,17 @@
/*
* 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 "OSSBolts.h"
NS_ASSUME_NONNULL_BEGIN
NSString *const OSSBoltsFrameworkVersionString = @"1.7.0";
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,42 @@
/*
* 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 <Foundation/Foundation.h>
#import "OSSCancellationTokenRegistration.h"
NS_ASSUME_NONNULL_BEGIN
/*!
A block that will be called when a token is cancelled.
*/
typedef void(^OSSCancellationBlock)(void);
/*!
The consumer view of a CancellationToken.
Propagates notification that operations should be canceled.
A OSSCancellationToken has methods to inspect whether the token has been cancelled.
*/
@interface OSSCancellationToken : NSObject
/*!
Whether cancellation has been requested for this token source.
*/
@property (nonatomic, assign, readonly, getter=isCancellationRequested) BOOL cancellationRequested;
/*!
Register a block to be notified when the token is cancelled.
If the token is already cancelled the delegate will be notified immediately.
*/
- (OSSCancellationTokenRegistration *)registerCancellationObserverWithBlock:(OSSCancellationBlock)block;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,144 @@
/*
* 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 "OSSCancellationToken.h"
#import "OSSCancellationTokenRegistration.h"
NS_ASSUME_NONNULL_BEGIN
@interface OSSCancellationToken ()
@property (nullable, nonatomic, strong) NSMutableArray *registrations;
@property (nonatomic, strong) NSObject *lock;
@property (nonatomic) BOOL disposed;
@end
@interface OSSCancellationTokenRegistration (OSSCancellationToken)
+ (instancetype)registrationWithToken:(OSSCancellationToken *)token delegate:(OSSCancellationBlock)delegate;
- (void)notifyDelegate;
@end
@implementation OSSCancellationToken
@synthesize cancellationRequested = _cancellationRequested;
#pragma mark - Initializer
- (instancetype)init {
self = [super init];
if (!self) return self;
_registrations = [NSMutableArray array];
_lock = [NSObject new];
return self;
}
#pragma mark - Custom Setters/Getters
- (BOOL)isCancellationRequested {
@synchronized(self.lock) {
[self throwIfDisposed];
return _cancellationRequested;
}
}
- (void)cancel {
NSArray *registrations;
@synchronized(self.lock) {
[self throwIfDisposed];
if (_cancellationRequested) {
return;
}
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(cancelPrivate) object:nil];
_cancellationRequested = YES;
registrations = [self.registrations copy];
}
[self notifyCancellation:registrations];
}
- (void)notifyCancellation:(NSArray *)registrations {
for (OSSCancellationTokenRegistration *registration in registrations) {
[registration notifyDelegate];
}
}
- (OSSCancellationTokenRegistration *)registerCancellationObserverWithBlock:(OSSCancellationBlock)block {
@synchronized(self.lock) {
OSSCancellationTokenRegistration *registration = [OSSCancellationTokenRegistration registrationWithToken:self delegate:[block copy]];
[self.registrations addObject:registration];
return registration;
}
}
- (void)unregisterRegistration:(OSSCancellationTokenRegistration *)registration {
@synchronized(self.lock) {
[self throwIfDisposed];
[self.registrations removeObject:registration];
}
}
// Delay on a non-public method to prevent interference with a user calling performSelector or
// cancelPreviousPerformRequestsWithTarget on the public method
- (void)cancelPrivate {
[self cancel];
}
- (void)cancelAfterDelay:(int)millis {
[self throwIfDisposed];
if (millis < -1) {
[NSException raise:NSInvalidArgumentException format:@"Delay must be >= -1"];
}
if (millis == 0) {
[self cancel];
return;
}
@synchronized(self.lock) {
[self throwIfDisposed];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(cancelPrivate) object:nil];
if (self.cancellationRequested) {
return;
}
if (millis != -1) {
double delay = (double)millis / 1000;
[self performSelector:@selector(cancelPrivate) withObject:nil afterDelay:delay];
}
}
}
- (void)dispose {
@synchronized(self.lock) {
if (self.disposed) {
return;
}
[self.registrations makeObjectsPerformSelector:@selector(dispose)];
self.registrations = nil;
self.disposed = YES;
}
}
- (void)throwIfDisposed {
if (self.disposed) {
[NSException raise:NSInternalInconsistencyException format:@"Object already disposed"];
}
}
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,29 @@
/*
* 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/*!
Represents the registration of a cancellation observer with a cancellation token.
Can be used to unregister the observer at a later time.
*/
@interface OSSCancellationTokenRegistration : NSObject
/*!
Removes the cancellation observer registered with the token
and releases all resources associated with this registration.
*/
- (void)dispose;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,79 @@
/*
* 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 "OSSCancellationTokenRegistration.h"
#import "OSSCancellationToken.h"
NS_ASSUME_NONNULL_BEGIN
@interface OSSCancellationTokenRegistration ()
@property (nonatomic, weak) OSSCancellationToken *token;
@property (nullable, nonatomic, strong) OSSCancellationBlock cancellationObserverBlock;
@property (nonatomic, strong) NSObject *lock;
@property (nonatomic) BOOL disposed;
@end
@interface OSSCancellationToken (OSSCancellationTokenRegistration)
- (void)unregisterRegistration:(OSSCancellationTokenRegistration *)registration;
@end
@implementation OSSCancellationTokenRegistration
+ (instancetype)registrationWithToken:(OSSCancellationToken *)token delegate:(OSSCancellationBlock)delegate {
OSSCancellationTokenRegistration *registration = [OSSCancellationTokenRegistration new];
registration.token = token;
registration.cancellationObserverBlock = delegate;
return registration;
}
- (instancetype)init {
self = [super init];
if (!self) return self;
_lock = [NSObject new];
return self;
}
- (void)dispose {
@synchronized(self.lock) {
if (self.disposed) {
return;
}
self.disposed = YES;
}
OSSCancellationToken *token = self.token;
if (token != nil) {
[token unregisterRegistration:self];
self.token = nil;
}
self.cancellationObserverBlock = nil;
}
- (void)notifyDelegate {
@synchronized(self.lock) {
[self throwIfDisposed];
self.cancellationObserverBlock();
}
}
- (void)throwIfDisposed {
NSAssert(!self.disposed, @"Object already disposed");
}
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,60 @@
/*
* 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class OSSCancellationToken;
/*!
OSSCancellationTokenSource represents the producer side of a CancellationToken.
Signals to a CancellationToken that it should be canceled.
It is a cancellation token that also has methods
for changing the state of a token by cancelling it.
*/
@interface OSSCancellationTokenSource : NSObject
/*!
Creates a new cancellation token source.
*/
+ (instancetype)cancellationTokenSource;
/*!
The cancellation token associated with this CancellationTokenSource.
*/
@property (nonatomic, strong, readonly) OSSCancellationToken *token;
/*!
Whether cancellation has been requested for this token source.
*/
@property (nonatomic, assign, readonly, getter=isCancellationRequested) BOOL cancellationRequested;
/*!
Cancels the token if it has not already been cancelled.
*/
- (void)cancel;
/*!
Schedules a cancel operation on this CancellationTokenSource after the specified number of milliseconds.
@param millis The number of milliseconds to wait before completing the returned task.
If delay is `0` the cancel is executed immediately. If delay is `-1` any scheduled cancellation is stopped.
*/
- (void)cancelAfterDelay:(int)millis;
/*!
Releases all resources associated with this token source,
including disposing of all registrations.
*/
- (void)dispose;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,64 @@
/*
* 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 "OSSCancellationTokenSource.h"
#import "OSSCancellationToken.h"
NS_ASSUME_NONNULL_BEGIN
@interface OSSCancellationToken (OSSCancellationTokenSource)
- (void)cancel;
- (void)cancelAfterDelay:(int)millis;
- (void)dispose;
- (void)throwIfDisposed;
@end
@implementation OSSCancellationTokenSource
#pragma mark - Initializer
- (instancetype)init {
self = [super init];
if (!self) return self;
_token = [OSSCancellationToken new];
return self;
}
+ (instancetype)cancellationTokenSource {
return [OSSCancellationTokenSource new];
}
#pragma mark - Custom Setters/Getters
- (BOOL)isCancellationRequested {
return _token.isCancellationRequested;
}
- (void)cancel {
[_token cancel];
}
- (void)cancelAfterDelay:(int)millis {
[_token cancelAfterDelay:millis];
}
- (void)dispose {
[_token dispose];
}
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,62 @@
/*
* 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/*!
An object that can run a given block.
*/
@interface OSSExecutor : NSObject
/*!
Returns a default executor, which runs continuations immediately until the call stack gets too
deep, then dispatches to a new GCD queue.
*/
+ (instancetype)defaultExecutor;
/*!
Returns an executor that runs continuations on the thread where the previous task was completed.
*/
+ (instancetype)immediateExecutor;
/*!
Returns an executor that runs continuations on the main thread.
*/
+ (instancetype)mainThreadExecutor;
/*!
Returns a new executor that uses the given block to execute continuations.
@param block The block to use.
*/
+ (instancetype)executorWithBlock:(void(^)(void(^block)(void)))block;
/*!
Returns a new executor that runs continuations on the given queue.
@param queue The instance of `dispatch_queue_t` to dispatch all continuations onto.
*/
+ (instancetype)executorWithDispatchQueue:(dispatch_queue_t)queue;
/*!
Returns a new executor that runs continuations on the given queue.
@param queue The instance of `NSOperationQueue` to run all continuations on.
*/
+ (instancetype)executorWithOperationQueue:(NSOperationQueue *)queue;
/*!
Runs the given block using this executor's particular strategy.
@param block The block to execute.
*/
- (void)execute:(void(^)(void))block;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,136 @@
/*
* 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 "OSSExecutor.h"
#import <pthread.h>
NS_ASSUME_NONNULL_BEGIN
/*!
Get the remaining stack-size of the current thread.
@param totalSize The total stack size of the current thread.
@return The remaining size, in bytes, available to the current thread.
@note This function cannot be inlined, as otherwise the internal implementation could fail to report the proper
remaining stack space.
*/
__attribute__((noinline)) static size_t remaining_stack_size(size_t *restrict totalSize) {
pthread_t currentThread = pthread_self();
// NOTE: We must store stack pointers as uint8_t so that the pointer math is well-defined
uint8_t *endStack = pthread_get_stackaddr_np(currentThread);
*totalSize = pthread_get_stacksize_np(currentThread);
// NOTE: If the function is inlined, this value could be incorrect
uint8_t *frameAddr = __builtin_frame_address(0);
return (*totalSize) - (endStack - frameAddr);
}
@interface OSSExecutor ()
@property (nonatomic, copy) void(^block)(void(^block)(void));
@end
@implementation OSSExecutor
#pragma mark - Executor methods
+ (instancetype)defaultExecutor {
static OSSExecutor *defaultExecutor = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
defaultExecutor = [self executorWithBlock:^void(void(^block)(void)) {
// We prefer to run everything possible immediately, so that there is callstack information
// when debugging. However, we don't want the stack to get too deep, so if the remaining stack space
// is less than 10% of the total space, we dispatch to another GCD queue.
size_t totalStackSize = 0;
size_t remainingStackSize = remaining_stack_size(&totalStackSize);
if (remainingStackSize < (totalStackSize / 10)) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block);
} else {
@autoreleasepool {
block();
}
}
}];
});
return defaultExecutor;
}
+ (instancetype)immediateExecutor {
static OSSExecutor *immediateExecutor = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
immediateExecutor = [self executorWithBlock:^void(void(^block)(void)) {
block();
}];
});
return immediateExecutor;
}
+ (instancetype)mainThreadExecutor {
static OSSExecutor *mainThreadExecutor = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
mainThreadExecutor = [self executorWithBlock:^void(void(^block)(void)) {
if (![NSThread isMainThread]) {
dispatch_async(dispatch_get_main_queue(), block);
} else {
@autoreleasepool {
block();
}
}
}];
});
return mainThreadExecutor;
}
+ (instancetype)executorWithBlock:(void(^)(void(^block)(void)))block {
return [[self alloc] initWithBlock:block];
}
+ (instancetype)executorWithDispatchQueue:(dispatch_queue_t)queue {
return [self executorWithBlock:^void(void(^block)(void)) {
dispatch_async(queue, block);
}];
}
+ (instancetype)executorWithOperationQueue:(NSOperationQueue *)queue {
return [self executorWithBlock:^void(void(^block)(void)) {
[queue addOperation:[NSBlockOperation blockOperationWithBlock:block]];
}];
}
#pragma mark - Initializer
- (instancetype)initWithBlock:(void(^)(void(^block)(void)))block {
self = [super init];
if (!self) return self;
_block = block;
return self;
}
#pragma mark - Execution
- (void)execute:(void(^)(void))block {
self.block(block);
}
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,295 @@
/*
* 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 <Foundation/Foundation.h>
#import "OSSCancellationToken.h"
NS_ASSUME_NONNULL_BEGIN
/*!
Error domain used if there was multiple errors on <OSSTask taskForCompletionOfAllTasks:>.
*/
extern NSString *const OSSTaskErrorDomain;
/*!
An error code used for <OSSTask taskForCompletionOfAllTasks:>, if there were multiple errors.
*/
extern NSInteger const kOSSMultipleErrorsError;
/*!
An exception that is thrown if there was multiple exceptions on <OSSTask taskForCompletionOfAllTasks:>.
*/
extern NSString *const OSSTaskMultipleExceptionsException;
/*!
An error userInfo key used if there were multiple errors on <OSSTask taskForCompletionOfAllTasks:>.
Value type is `NSArray<NSError *> *`.
*/
extern NSString *const OSSTaskMultipleErrorsUserInfoKey;
/*!
An error userInfo key used if there were multiple exceptions on <OSSTask taskForCompletionOfAllTasks:>.
Value type is `NSArray<NSException *> *`.
*/
extern NSString *const OSSTaskMultipleExceptionsUserInfoKey;
@class OSSExecutor;
@class OSSTask;
/*!
The consumer view of a Task. A OSSTask has methods to
inspect the state of the task, and to add continuations to
be run once the task is complete.
*/
@interface OSSTask<__covariant ResultType> : NSObject
/*!
A block that can act as a continuation for a task.
*/
typedef __nullable id(^OSSContinuationBlock)(OSSTask<ResultType> *task);
/*!
Creates a task that is already completed with the given result.
@param result The result for the task.
*/
+ (instancetype)taskWithResult:(_Nullable ResultType)result;
/*!
Creates a task that is already completed with the given error.
@param error The error for the task.
*/
+ (instancetype)taskWithError:(NSError *)error;
/*!
Creates a task that is already completed with the given exception.
@param exception The exception for the task.
*/
+ (instancetype)taskWithException:(NSException *)exception;
/*!
Creates a task that is already cancelled.
*/
+ (instancetype)cancelledTask;
/*!
Returns a task that will be completed (with result == nil) once
all of the input tasks have completed.
@param tasks An `NSArray` of the tasks to use as an input.
*/
+ (instancetype)taskForCompletionOfAllTasks:(nullable NSArray<OSSTask *> *)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:(nullable NSArray<OSSTask *> *)tasks;
/*!
Returns a task that will be completed once there is at least one successful task.
The first task to successuly complete will set the result, all other tasks results are
ignored.
@param tasks An `NSArray` of the tasks to use as an input.
*/
+ (instancetype)taskForCompletionOfAnyTask:(nullable NSArray<OSSTask *> *)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
task will be finished (with result == nil).
*/
+ (instancetype)taskWithDelay:(int)millis;
/*!
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
task will be finished (with result == nil).
@param token The cancellation token (optional).
*/
+ (instancetype)taskWithDelay:(int)millis cancellationToken:(nullable OSSCancellationToken *)token;
/*!
Returns a task that will be completed after the given block completes with
the specified executor.
@param executor A OSSExecutor responsible for determining how the
continuation block will be run.
@param block The block to immediately schedule to run with the given executor.
@returns A task that will be completed after block has run.
If block returns a OSSTask, then the task returned from
this method will not be completed until that task is completed.
*/
+ (instancetype)taskFromExecutor:(OSSExecutor *)executor withBlock:(nullable id (^)(void))block;
// Properties that will be set on the task once it is completed.
/*!
The result of a successful task.
*/
@property (nullable, nonatomic, strong, readonly) ResultType result;
/*!
The error of a failed task.
*/
@property (nullable, nonatomic, strong, readonly) NSError *error;
/*!
The exception of a failed task.
*/
@property (nullable, nonatomic, strong, readonly) NSException *exception;
/*!
Whether this task has been cancelled.
*/
@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.
*/
@property (nonatomic, assign, readonly, getter=isCompleted) BOOL completed;
/*!
Enqueues the given block to be run once this task is complete.
This method uses a default execution strategy. The block will be
run on the thread where the previous task completes, unless the
the stack depth is too deep, in which case it will be run on a
dispatch queue with default priority.
@param block The block to be run once this task is complete.
@returns A task that will be completed after block has run.
If block returns a OSSTask, then the task returned from
this method will not be completed until that task is completed.
*/
- (OSSTask *)continueWithBlock:(OSSContinuationBlock)block;
/*!
Enqueues the given block to be run once this task is complete.
This method uses a default execution strategy. The block will be
run on the thread where the previous task completes, unless the
the stack depth is too deep, in which case it will be run on a
dispatch queue with default priority.
@param block The block to be run once this task is complete.
@param cancellationToken The cancellation token (optional).
@returns A task that will be completed after block has run.
If block returns a OSSTask, then the task returned from
this method will not be completed until that task is completed.
*/
- (OSSTask *)continueWithBlock:(OSSContinuationBlock)block cancellationToken:(nullable OSSCancellationToken *)cancellationToken;
/*!
Enqueues the given block to be run once this task is complete.
@param executor A OSSExecutor responsible for determining how the
continuation block will be run.
@param block The block to be run once this task is complete.
@returns A task that will be completed after block has run.
If block returns a OSSTask, then the task returned from
this method will not be completed until that task is completed.
*/
- (OSSTask *)continueWithExecutor:(OSSExecutor *)executor withBlock:(OSSContinuationBlock)block;
/*!
Enqueues the given block to be run once this task is complete.
@param executor A OSSExecutor responsible for determining how the
continuation block will be run.
@param block The block to be run once this task is complete.
@param cancellationToken The cancellation token (optional).
@returns A task that will be completed after block has run.
If block returns a OSSTask, then the task returned from
his method will not be completed until that task is completed.
*/
- (OSSTask *)continueWithExecutor:(OSSExecutor *)executor
block:(OSSContinuationBlock)block
cancellationToken:(nullable OSSCancellationToken *)cancellationToken;
/*!
Identical to continueWithBlock:, except that the block is only run
if this task did not produce a cancellation, error, or exception.
If it did, then the failure will be propagated to the returned
task.
@param block The block to be run once this task is complete.
@returns A task that will be completed after block has run.
If block returns a OSSTask, then the task returned from
this method will not be completed until that task is completed.
*/
- (OSSTask *)continueWithSuccessBlock:(OSSContinuationBlock)block;
/*!
Identical to continueWithBlock:, except that the block is only run
if this task did not produce a cancellation, error, or exception.
If it did, then the failure will be propagated to the returned
task.
@param block The block to be run once this task is complete.
@param cancellationToken The cancellation token (optional).
@returns A task that will be completed after block has run.
If block returns a OSSTask, then the task returned from
this method will not be completed until that task is completed.
*/
- (OSSTask *)continueWithSuccessBlock:(OSSContinuationBlock)block cancellationToken:(nullable OSSCancellationToken *)cancellationToken;
/*!
Identical to continueWithExecutor:withBlock:, except that the block
is only run if this task did not produce a cancellation, error, or
exception. If it did, then the failure will be propagated to the
returned task.
@param executor A OSSExecutor responsible for determining how the
continuation block will be run.
@param block The block to be run once this task is complete.
@returns A task that will be completed after block has run.
If block returns a OSSTask, then the task returned from
this method will not be completed until that task is completed.
*/
- (OSSTask *)continueWithExecutor:(OSSExecutor *)executor withSuccessBlock:(OSSContinuationBlock)block;
/*!
Identical to continueWithExecutor:withBlock:, except that the block
is only run if this task did not produce a cancellation, error, or
exception. If it did, then the failure will be propagated to the
returned task.
@param executor A OSSExecutor responsible for determining how the
continuation block will be run.
@param block The block to be run once this task is complete.
@param cancellationToken The cancellation token (optional).
@returns A task that will be completed after block has run.
If block returns a OSSTask, then the task returned from
this method will not be completed until that task is completed.
*/
- (OSSTask *)continueWithExecutor:(OSSExecutor *)executor
successBlock:(OSSContinuationBlock)block
cancellationToken:(nullable OSSCancellationToken *)cancellationToken;
/*!
Waits until this operation is completed.
This method is inefficient and consumes a thread resource while
it's running. It should be avoided. This method logs a warning
message if it is used on the main thread.
*/
- (void)waitUntilFinished;
@end
@class OSSResult;
@interface OSSTask(OSS)
typedef void(^OSSCompleteBlock)(BOOL isSuccess, NSError * _Nullable error, OSSResult * _Nullable result);
- (BOOL)isSuccessful;
- (NSError *)toError;
- (OSSTask *)completed:(OSSCompleteBlock)block;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,584 @@
/*
* 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 "OSSConstants.h"
#import "OSSDefine.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) {
NSError *error = [NSError errorWithDomain:OSSClientErrorDomain
code:OSSClientErrorCodeExcpetionCatched
userInfo:@{OSSErrorMessageTOKEN: [NSString stringWithFormat:@"Catch exception - %@", exception]}];
tcs.error = error;
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) {
NSError *error = [NSError errorWithDomain:OSSClientErrorDomain
code:OSSClientErrorCodeExcpetionCatched
userInfo:@{OSSErrorMessageTOKEN: [NSString stringWithFormat:@"Catch exception - %@", task.exception]}];
tcs.error = error;
} 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
@implementation OSSTask(OSS)
- (BOOL)isSuccessful {
if (self.cancelled || self.faulted) {
return false;
}
return true;
}
- (NSError *)toError {
if (self.cancelled) {
return [NSError errorWithDomain:OSSClientErrorDomain
code:OSSClientErrorCodeTaskCancelled
userInfo:@{OSSErrorMessageTOKEN: @"This task is cancelled"}];
} else if (self.error) {
return self.error;
} else if (self.exception) {
return [NSError errorWithDomain:OSSClientErrorDomain
code:OSSClientErrorCodeExcpetionCatched
userInfo:@{OSSErrorMessageTOKEN: [NSString stringWithFormat:@"Catch exception - %@", self.exception]}];
}
return nil;
}
- (OSSTask *)completed:(OSSCompleteBlock)block {
return [self continueWithBlock:^id _Nullable(OSSTask * _Nonnull task) {
if ([task isSuccessful]) {
block(YES, nil, task.result);
} else {
block(NO, [task toError], nil);
}
return nil;
}];
}
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,89 @@
/*
* 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class OSSTask<ResultType>;
/*!
A OSSTaskCompletionSource represents the producer side of tasks.
It is a task that also has methods for changing the state of the
task by settings its completion values.
*/
@interface OSSTaskCompletionSource<__covariant ResultType> : NSObject
/*!
Creates a new unfinished task.
*/
+ (instancetype)taskCompletionSource;
/*!
The task associated with this TaskCompletionSource.
*/
@property (nonatomic, strong, readonly) OSSTask<ResultType> *task;
/*!
Completes the task by setting the result.
Attempting to set this for a completed task will raise an exception.
@param result The result of the task.
*/
- (void)setResult:(nullable ResultType)result;
/*!
Completes the task by setting the error.
Attempting to set this for a completed task will raise an exception.
@param error The error for the task.
*/
- (void)setError:(NSError *)error;
/*!
Completes the task by setting an exception.
Attempting to set this for a completed task will raise an exception.
@param exception The exception for the task.
*/
- (void)setException:(NSException *)exception;
/*!
Completes the task by marking it as cancelled.
Attempting to set this for a completed task will raise an exception.
*/
- (void)cancel;
/*!
Sets the result of the task if it wasn't already completed.
@returns whether the new value was set.
*/
- (BOOL)trySetResult:(nullable ResultType)result;
/*!
Sets the error of the task if it wasn't already completed.
@param error The error for the task.
@returns whether the new value was set.
*/
- (BOOL)trySetError:(NSError *)error;
/*!
Sets the exception of the task if it wasn't already completed.
@param exception The exception for the task.
@returns whether the new value was set.
*/
- (BOOL)trySetException:(NSException *)exception;
/*!
Sets the cancellation state of the task if it wasn't already completed.
@returns whether the new value was set.
*/
- (BOOL)trySetCancelled;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,91 @@
/*
* 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 "OSSTaskCompletionSource.h"
#import "OSSTask.h"
NS_ASSUME_NONNULL_BEGIN
@interface OSSTask (OSSTaskCompletionSource)
- (BOOL)trySetResult:(nullable id)result;
- (BOOL)trySetError:(NSError *)error;
- (BOOL)trySetException:(NSException *)exception;
- (BOOL)trySetCancelled;
@end
@implementation OSSTaskCompletionSource
#pragma mark - Initializer
+ (instancetype)taskCompletionSource {
return [[self alloc] init];
}
- (instancetype)init {
self = [super init];
if (!self) return self;
_task = [[OSSTask alloc] init];
return self;
}
#pragma mark - Custom Setters/Getters
- (void)setResult:(nullable id)result {
if (![self.task trySetResult:result]) {
[NSException raise:NSInternalInconsistencyException
format:@"Cannot set the result on a completed task."];
}
}
- (void)setError:(NSError *)error {
if (![self.task trySetError:error]) {
[NSException raise:NSInternalInconsistencyException
format:@"Cannot set the error on a completed task."];
}
}
- (void)setException:(NSException *)exception {
if (![self.task trySetException:exception]) {
[NSException raise:NSInternalInconsistencyException
format:@"Cannot set the exception on a completed task."];
}
}
- (void)cancel {
if (![self.task trySetCancelled]) {
[NSException raise:NSInternalInconsistencyException
format:@"Cannot cancel a completed task."];
}
}
- (BOOL)trySetResult:(nullable id)result {
return [self.task trySetResult:result];
}
- (BOOL)trySetError:(NSError *)error {
return [self.task trySetError:error];
}
- (BOOL)trySetException:(NSException *)exception {
return [self.task trySetException:exception];
}
- (BOOL)trySetCancelled {
return [self.task trySetCancelled];
}
@end
NS_ASSUME_NONNULL_END