- Add Facebook SDK
- Add PNUser Login
This commit is contained in:
1 parent
f2885301b9
commit
a6dcf672bb
370 files changed
+44655
-2705
No files matched your search
@@ -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 <Bolts/BFCancellationTokenRegistration.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/*!
|
||||
A block that will be called when a token is cancelled.
|
||||
*/
|
||||
typedef void(^BFCancellationBlock)();
|
||||
|
||||
/*!
|
||||
The consumer view of a CancellationToken.
|
||||
Propagates notification that operations should be canceled.
|
||||
A BFCancellationToken has methods to inspect whether the token has been cancelled.
|
||||
*/
|
||||
@interface BFCancellationToken : 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.
|
||||
*/
|
||||
- (BFCancellationTokenRegistration *)registerCancellationObserverWithBlock:(BFCancellationBlock)block;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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 "BFCancellationToken.h"
|
||||
#import "BFCancellationTokenRegistration.h"
|
||||
|
||||
@interface BFCancellationToken ()
|
||||
|
||||
@property (nonatomic, assign, getter=isCancellationRequested) BOOL cancellationRequested;
|
||||
@property (nonatomic, strong) NSMutableArray *registrations;
|
||||
@property (nonatomic, strong) NSObject *lock;
|
||||
@property (nonatomic) BOOL disposed;
|
||||
|
||||
@end
|
||||
|
||||
@interface BFCancellationTokenRegistration (BFCancellationToken)
|
||||
|
||||
+ (instancetype)registrationWithToken:(BFCancellationToken *)token delegate:(BFCancellationBlock)delegate;
|
||||
|
||||
- (void)notifyDelegate;
|
||||
|
||||
@end
|
||||
|
||||
@implementation BFCancellationToken
|
||||
|
||||
#pragma mark - Initializer
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (!self) return nil;
|
||||
|
||||
_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 (BFCancellationTokenRegistration *registration in registrations) {
|
||||
[registration notifyDelegate];
|
||||
}
|
||||
}
|
||||
|
||||
- (BFCancellationTokenRegistration *)registerCancellationObserverWithBlock:(BFCancellationBlock)block {
|
||||
@synchronized(self.lock) {
|
||||
BFCancellationTokenRegistration *registration = [BFCancellationTokenRegistration registrationWithToken:self delegate:[block copy]];
|
||||
[self.registrations addObject:registration];
|
||||
|
||||
return registration;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)unregisterRegistration:(BFCancellationTokenRegistration *)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.disposed = YES;
|
||||
for (BFCancellationTokenRegistration *registration in self.registrations) {
|
||||
[registration dispose];
|
||||
}
|
||||
[self.registrations removeAllObjects];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)throwIfDisposed {
|
||||
if (self.disposed) {
|
||||
[NSException raise:NSInternalInconsistencyException format:@"Object already disposed"];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -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 BFCancellationTokenRegistration : NSObject
|
||||
|
||||
/*!
|
||||
Removes the cancellation observer registered with the token
|
||||
and releases all resources associated with this registration.
|
||||
*/
|
||||
- (void)dispose;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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 "BFCancellationTokenRegistration.h"
|
||||
|
||||
#import "BFCancellationToken.h"
|
||||
|
||||
@interface BFCancellationTokenRegistration ()
|
||||
|
||||
@property (nonatomic, weak) BFCancellationToken *token;
|
||||
@property (nonatomic, strong) BFCancellationBlock cancellationObserverBlock;
|
||||
@property (nonatomic, strong) NSObject *lock;
|
||||
@property (nonatomic) BOOL disposed;
|
||||
|
||||
@end
|
||||
|
||||
@interface BFCancellationToken (BFCancellationTokenRegistration)
|
||||
|
||||
- (void)unregisterRegistration:(BFCancellationTokenRegistration *)registration;
|
||||
|
||||
@end
|
||||
|
||||
@implementation BFCancellationTokenRegistration
|
||||
|
||||
+ (instancetype)registrationWithToken:(BFCancellationToken *)token delegate:(BFCancellationBlock)delegate {
|
||||
BFCancellationTokenRegistration *registration = [BFCancellationTokenRegistration new];
|
||||
registration.token = token;
|
||||
registration.cancellationObserverBlock = delegate;
|
||||
return registration;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (!self) return nil;
|
||||
|
||||
_lock = [NSObject new];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dispose {
|
||||
@synchronized(self.lock) {
|
||||
if (self.disposed) {
|
||||
return;
|
||||
}
|
||||
self.disposed = YES;
|
||||
}
|
||||
|
||||
BFCancellationToken *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
|
||||
@@ -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 BFCancellationToken;
|
||||
|
||||
/*!
|
||||
BFCancellationTokenSource 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 BFCancellationTokenSource : NSObject
|
||||
|
||||
/*!
|
||||
Creates a new cancellation token source.
|
||||
*/
|
||||
+ (instancetype)cancellationTokenSource;
|
||||
|
||||
/*!
|
||||
The cancellation token associated with this CancellationTokenSource.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) BFCancellationToken *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
|
||||
@@ -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 "BFCancellationTokenSource.h"
|
||||
|
||||
#import "BFCancellationToken.h"
|
||||
|
||||
@interface BFCancellationToken (BFCancellationTokenSource)
|
||||
|
||||
- (void)cancel;
|
||||
- (void)cancelAfterDelay:(int)millis;
|
||||
|
||||
- (void)dispose;
|
||||
- (void)throwIfDisposed;
|
||||
|
||||
@end
|
||||
|
||||
@implementation BFCancellationTokenSource
|
||||
|
||||
#pragma mark - Initializer
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (!self) return nil;
|
||||
|
||||
_token = [BFCancellationToken new];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (instancetype)cancellationTokenSource {
|
||||
return [BFCancellationTokenSource 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
|
||||
+62
@@ -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 BFExecutor : 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)()))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(^)())block;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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 "BFExecutor.h"
|
||||
|
||||
#import <pthread.h>
|
||||
|
||||
/*!
|
||||
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 *__nonnull 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 BFExecutor ()
|
||||
|
||||
@property (nonatomic, copy) void(^block)(void(^block)());
|
||||
|
||||
@end
|
||||
|
||||
@implementation BFExecutor
|
||||
|
||||
#pragma mark - Executor methods
|
||||
|
||||
+ (instancetype)defaultExecutor {
|
||||
static BFExecutor *defaultExecutor = NULL;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
defaultExecutor = [self executorWithBlock:^void(void(^block)()) {
|
||||
// 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 BFExecutor *immediateExecutor = NULL;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
immediateExecutor = [self executorWithBlock:^void(void(^block)()) {
|
||||
block();
|
||||
}];
|
||||
});
|
||||
return immediateExecutor;
|
||||
}
|
||||
|
||||
+ (instancetype)mainThreadExecutor {
|
||||
static BFExecutor *mainThreadExecutor = NULL;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
mainThreadExecutor = [self executorWithBlock:^void(void(^block)()) {
|
||||
if (![NSThread isMainThread]) {
|
||||
dispatch_async(dispatch_get_main_queue(), block);
|
||||
} else {
|
||||
@autoreleasepool {
|
||||
block();
|
||||
}
|
||||
}
|
||||
}];
|
||||
});
|
||||
return mainThreadExecutor;
|
||||
}
|
||||
|
||||
+ (instancetype)executorWithBlock:(void(^)(void(^block)()))block {
|
||||
return [[self alloc] initWithBlock:block];
|
||||
}
|
||||
|
||||
+ (instancetype)executorWithDispatchQueue:(dispatch_queue_t)queue {
|
||||
return [self executorWithBlock:^void(void(^block)()) {
|
||||
dispatch_async(queue, block);
|
||||
}];
|
||||
}
|
||||
|
||||
+ (instancetype)executorWithOperationQueue:(NSOperationQueue *)queue {
|
||||
return [self executorWithBlock:^void(void(^block)()) {
|
||||
[queue addOperation:[NSBlockOperation blockOperationWithBlock:block]];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Initializer
|
||||
|
||||
- (instancetype)initWithBlock:(void(^)(void(^block)()))block {
|
||||
self = [super init];
|
||||
if (!self) return nil;
|
||||
|
||||
_block = block;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Execution
|
||||
|
||||
- (void)execute:(void(^)())block {
|
||||
self.block(block);
|
||||
}
|
||||
|
||||
@end
|
||||
Generated
+261
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
* 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 <Bolts/BFCancellationToken.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/*!
|
||||
Error domain used if there was multiple errors on <BFTask taskForCompletionOfAllTasks:>.
|
||||
*/
|
||||
extern NSString *const BFTaskErrorDomain;
|
||||
|
||||
/*!
|
||||
An error code used for <BFTask taskForCompletionOfAllTasks:>, if there were multiple errors.
|
||||
*/
|
||||
extern NSInteger const kBFMultipleErrorsError;
|
||||
|
||||
/*!
|
||||
An exception that is thrown if there was multiple exceptions on <BFTask taskForCompletionOfAllTasks:>.
|
||||
*/
|
||||
extern NSString *const BFTaskMultipleExceptionsException;
|
||||
|
||||
@class BFExecutor;
|
||||
@class BFTask;
|
||||
|
||||
/*!
|
||||
The consumer view of a Task. A BFTask has methods to
|
||||
inspect the state of the task, and to add continuations to
|
||||
be run once the task is complete.
|
||||
*/
|
||||
@interface BFTask<__covariant ResultType> : NSObject
|
||||
|
||||
/*!
|
||||
A block that can act as a continuation for a task.
|
||||
*/
|
||||
typedef __nullable id(^BFContinuationBlock)(BFTask<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<BFTask *> *)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<BFTask *> *)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 BFCancellationToken *)token;
|
||||
|
||||
/*!
|
||||
Returns a task that will be completed after the given block completes with
|
||||
the specified executor.
|
||||
@param executor A BFExecutor 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 BFTask, then the task returned from
|
||||
this method will not be completed until that task is completed.
|
||||
*/
|
||||
+ (instancetype)taskFromExecutor:(BFExecutor *)executor withBlock:(nullable id (^)())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 BFTask, then the task returned from
|
||||
this method will not be completed until that task is completed.
|
||||
*/
|
||||
- (BFTask *)continueWithBlock:(BFContinuationBlock)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 BFTask, then the task returned from
|
||||
this method will not be completed until that task is completed.
|
||||
*/
|
||||
- (BFTask *)continueWithBlock:(BFContinuationBlock)block cancellationToken:(nullable BFCancellationToken *)cancellationToken;
|
||||
|
||||
/*!
|
||||
Enqueues the given block to be run once this task is complete.
|
||||
@param executor A BFExecutor 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 BFTask, then the task returned from
|
||||
this method will not be completed until that task is completed.
|
||||
*/
|
||||
- (BFTask *)continueWithExecutor:(BFExecutor *)executor withBlock:(BFContinuationBlock)block;
|
||||
/*!
|
||||
Enqueues the given block to be run once this task is complete.
|
||||
@param executor A BFExecutor 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 BFTask, then the task returned from
|
||||
his method will not be completed until that task is completed.
|
||||
*/
|
||||
- (BFTask *)continueWithExecutor:(BFExecutor *)executor
|
||||
block:(BFContinuationBlock)block
|
||||
cancellationToken:(nullable BFCancellationToken *)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 BFTask, then the task returned from
|
||||
this method will not be completed until that task is completed.
|
||||
*/
|
||||
- (BFTask *)continueWithSuccessBlock:(BFContinuationBlock)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 BFTask, then the task returned from
|
||||
this method will not be completed until that task is completed.
|
||||
*/
|
||||
- (BFTask *)continueWithSuccessBlock:(BFContinuationBlock)block cancellationToken:(nullable BFCancellationToken *)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 BFExecutor 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 BFTask, then the task returned from
|
||||
this method will not be completed until that task is completed.
|
||||
*/
|
||||
- (BFTask *)continueWithExecutor:(BFExecutor *)executor withSuccessBlock:(BFContinuationBlock)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 BFExecutor 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 BFTask, then the task returned from
|
||||
this method will not be completed until that task is completed.
|
||||
*/
|
||||
- (BFTask *)continueWithExecutor:(BFExecutor *)executor
|
||||
successBlock:(BFContinuationBlock)block
|
||||
cancellationToken:(nullable BFCancellationToken *)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
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
Generated
+472
@@ -0,0 +1,472 @@
|
||||
/*
|
||||
* 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 "BFTask.h"
|
||||
|
||||
#import <libkern/OSAtomic.h>
|
||||
|
||||
#import "Bolts.h"
|
||||
|
||||
__attribute__ ((noinline)) void warnBlockingOperationOnMainThread() {
|
||||
NSLog(@"Warning: A long-running operation is being executed on the main thread. \n"
|
||||
" Break on warnBlockingOperationOnMainThread() to debug.");
|
||||
}
|
||||
|
||||
NSString *const BFTaskErrorDomain = @"bolts";
|
||||
NSInteger const kBFMultipleErrorsError = 80175001;
|
||||
NSString *const BFTaskMultipleExceptionsException = @"BFMultipleExceptionsException";
|
||||
|
||||
@interface BFTask () {
|
||||
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 BFTask
|
||||
|
||||
#pragma mark - Initializer
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (!self) return nil;
|
||||
|
||||
_lock = [[NSObject alloc] init];
|
||||
_condition = [[NSCondition alloc] init];
|
||||
_callbacks = [NSMutableArray array];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithResult:(id)result {
|
||||
self = [super init];
|
||||
if (!self) return nil;
|
||||
|
||||
[self trySetResult:result];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithError:(NSError *)error {
|
||||
self = [super init];
|
||||
if (!self) return nil;
|
||||
|
||||
[self trySetError:error];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithException:(NSException *)exception {
|
||||
self = [super init];
|
||||
if (!self) return nil;
|
||||
|
||||
[self trySetException:exception];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initCancelled {
|
||||
self = [super init];
|
||||
if (!self) return nil;
|
||||
|
||||
[self trySetCancelled];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Task Class methods
|
||||
|
||||
+ (instancetype)taskWithResult:(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:(NSArray<BFTask *> *)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];
|
||||
|
||||
BFTaskCompletionSource *tcs = [BFTaskCompletionSource taskCompletionSource];
|
||||
for (BFTask *task in tasks) {
|
||||
[task continueWithBlock:^id(BFTask *task) {
|
||||
if (task.exception) {
|
||||
@synchronized (lock) {
|
||||
[exceptions addObject:task.exception];
|
||||
}
|
||||
} else if (task.error) {
|
||||
@synchronized (lock) {
|
||||
[errors addObject:task.error];
|
||||
}
|
||||
} else if (task.cancelled) {
|
||||
OSAtomicIncrement32(&cancelled);
|
||||
}
|
||||
|
||||
if (OSAtomicDecrement32(&total) == 0) {
|
||||
if (exceptions.count > 0) {
|
||||
if (exceptions.count == 1) {
|
||||
tcs.exception = [exceptions firstObject];
|
||||
} else {
|
||||
NSException *exception =
|
||||
[NSException exceptionWithName:BFTaskMultipleExceptionsException
|
||||
reason:@"There were multiple exceptions."
|
||||
userInfo:@{ @"exceptions": exceptions }];
|
||||
tcs.exception = exception;
|
||||
}
|
||||
} else if (errors.count > 0) {
|
||||
if (errors.count == 1) {
|
||||
tcs.error = [errors firstObject];
|
||||
} else {
|
||||
NSError *error = [NSError errorWithDomain:BFTaskErrorDomain
|
||||
code:kBFMultipleErrorsError
|
||||
userInfo:@{ @"errors": errors }];
|
||||
tcs.error = error;
|
||||
}
|
||||
} else if (cancelled > 0) {
|
||||
[tcs cancel];
|
||||
} else {
|
||||
tcs.result = nil;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}];
|
||||
}
|
||||
return tcs.task;
|
||||
}
|
||||
|
||||
+ (instancetype)taskForCompletionOfAllTasksWithResults:(NSArray<BFTask *> *)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);
|
||||
dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
|
||||
tcs.result = nil;
|
||||
});
|
||||
return tcs.task;
|
||||
}
|
||||
|
||||
+ (instancetype)taskWithDelay:(int)millis
|
||||
cancellationToken:(BFCancellationToken *)token {
|
||||
if (token.cancellationRequested) {
|
||||
return [BFTask cancelledTask];
|
||||
}
|
||||
|
||||
BFTaskCompletionSource *tcs = [BFTaskCompletionSource 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:(BFExecutor *)executor withBlock:(nullable id (^)())block {
|
||||
return [[self taskWithResult:nil] continueWithExecutor:executor withBlock:^id(BFTask *task) {
|
||||
return block();
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Custom Setters/Getters
|
||||
|
||||
- (id)result {
|
||||
@synchronized(self.lock) {
|
||||
return _result;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)trySetResult:(id)result {
|
||||
@synchronized(self.lock) {
|
||||
if (self.completed) {
|
||||
return NO;
|
||||
}
|
||||
self.completed = YES;
|
||||
_result = result;
|
||||
[self runContinuations];
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (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;
|
||||
}
|
||||
}
|
||||
|
||||
- (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)setCompleted {
|
||||
@synchronized(self.lock) {
|
||||
_completed = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)runContinuations {
|
||||
@synchronized(self.lock) {
|
||||
[self.condition lock];
|
||||
[self.condition broadcast];
|
||||
[self.condition unlock];
|
||||
for (void (^callback)() in self.callbacks) {
|
||||
callback();
|
||||
}
|
||||
[self.callbacks removeAllObjects];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Chaining methods
|
||||
|
||||
- (BFTask *)continueWithExecutor:(BFExecutor *)executor
|
||||
withBlock:(BFContinuationBlock)block {
|
||||
return [self continueWithExecutor:executor block:block cancellationToken:nil];
|
||||
}
|
||||
|
||||
- (BFTask *)continueWithExecutor:(BFExecutor *)executor
|
||||
block:(BFContinuationBlock)block
|
||||
cancellationToken:(BFCancellationToken *)cancellationToken {
|
||||
BFTaskCompletionSource *tcs = [BFTaskCompletionSource taskCompletionSource];
|
||||
|
||||
// Capture all of the state that needs to used when the continuation is complete.
|
||||
void (^wrappedBlock)() = ^() {
|
||||
[executor execute:^{
|
||||
if (cancellationToken.cancellationRequested) {
|
||||
[tcs cancel];
|
||||
return;
|
||||
}
|
||||
|
||||
id result = nil;
|
||||
@try {
|
||||
result = block(self);
|
||||
} @catch (NSException *exception) {
|
||||
tcs.exception = exception;
|
||||
return;
|
||||
}
|
||||
|
||||
if ([result isKindOfClass:[BFTask class]]) {
|
||||
|
||||
id (^setupWithTask) (BFTask *) = ^id(BFTask *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;
|
||||
};
|
||||
|
||||
BFTask *resultTask = (BFTask *)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:[wrappedBlock copy]];
|
||||
}
|
||||
}
|
||||
if (completed) {
|
||||
wrappedBlock();
|
||||
}
|
||||
|
||||
return tcs.task;
|
||||
}
|
||||
|
||||
- (BFTask *)continueWithBlock:(BFContinuationBlock)block {
|
||||
return [self continueWithExecutor:[BFExecutor defaultExecutor] block:block cancellationToken:nil];
|
||||
}
|
||||
|
||||
- (BFTask *)continueWithBlock:(BFContinuationBlock)block
|
||||
cancellationToken:(BFCancellationToken *)cancellationToken {
|
||||
return [self continueWithExecutor:[BFExecutor defaultExecutor] block:block cancellationToken:cancellationToken];
|
||||
}
|
||||
|
||||
- (BFTask *)continueWithExecutor:(BFExecutor *)executor
|
||||
withSuccessBlock:(BFContinuationBlock)block {
|
||||
return [self continueWithExecutor:executor successBlock:block cancellationToken:nil];
|
||||
}
|
||||
|
||||
- (BFTask *)continueWithExecutor:(BFExecutor *)executor
|
||||
successBlock:(BFContinuationBlock)block
|
||||
cancellationToken:(BFCancellationToken *)cancellationToken {
|
||||
if (cancellationToken.cancellationRequested) {
|
||||
return [BFTask cancelledTask];
|
||||
}
|
||||
|
||||
return [self continueWithExecutor:executor block:^id(BFTask *task) {
|
||||
if (task.faulted || task.cancelled) {
|
||||
return task;
|
||||
} else {
|
||||
return block(task);
|
||||
}
|
||||
} cancellationToken:cancellationToken];
|
||||
}
|
||||
|
||||
- (BFTask *)continueWithSuccessBlock:(BFContinuationBlock)block {
|
||||
return [self continueWithExecutor:[BFExecutor defaultExecutor] successBlock:block cancellationToken:nil];
|
||||
}
|
||||
|
||||
- (BFTask *)continueWithSuccessBlock:(BFContinuationBlock)block
|
||||
cancellationToken:(BFCancellationToken *)cancellationToken {
|
||||
return [self continueWithExecutor:[BFExecutor defaultExecutor] successBlock:block cancellationToken:cancellationToken];
|
||||
}
|
||||
|
||||
#pragma mark - Syncing Task (Avoid it)
|
||||
|
||||
- (void)warnOperationOnMainThread {
|
||||
warnBlockingOperationOnMainThread();
|
||||
}
|
||||
|
||||
- (void)waitUntilFinished {
|
||||
if ([NSThread isMainThread]) {
|
||||
[self warnOperationOnMainThread];
|
||||
}
|
||||
|
||||
@synchronized(self.lock) {
|
||||
if (self.completed) {
|
||||
return;
|
||||
}
|
||||
[self.condition lock];
|
||||
}
|
||||
[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
|
||||
@@ -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 BFTask<ResultType>;
|
||||
|
||||
/*!
|
||||
A BFTaskCompletionSource 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 BFTaskCompletionSource<__covariant ResultType> : NSObject
|
||||
|
||||
/*!
|
||||
Creates a new unfinished task.
|
||||
*/
|
||||
+ (instancetype)taskCompletionSource;
|
||||
|
||||
/*!
|
||||
The task associated with this TaskCompletionSource.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) BFTask<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
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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 "BFTaskCompletionSource.h"
|
||||
|
||||
#import "BFTask.h"
|
||||
|
||||
@interface BFTaskCompletionSource ()
|
||||
|
||||
@property (nonatomic, strong, readwrite) BFTask *task;
|
||||
|
||||
@end
|
||||
|
||||
@interface BFTask (BFTaskCompletionSource)
|
||||
|
||||
- (BOOL)trySetResult:(id)result;
|
||||
- (BOOL)trySetError:(NSError *)error;
|
||||
- (BOOL)trySetException:(NSException *)exception;
|
||||
- (BOOL)trySetCancelled;
|
||||
|
||||
@end
|
||||
|
||||
@implementation BFTaskCompletionSource
|
||||
|
||||
#pragma mark - Initializer
|
||||
|
||||
+ (instancetype)taskCompletionSource {
|
||||
return [[self alloc] init];
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (!self) return nil;
|
||||
|
||||
_task = [[BFTask alloc] init];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Custom Setters/Getters
|
||||
|
||||
- (void)setResult:(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:(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
|
||||
Generated
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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 <Bolts/BoltsVersion.h>
|
||||
#import <Bolts/BFCancellationToken.h>
|
||||
#import <Bolts/BFCancellationTokenRegistration.h>
|
||||
#import <Bolts/BFCancellationTokenSource.h>
|
||||
#import <Bolts/BFExecutor.h>
|
||||
#import <Bolts/BFTask.h>
|
||||
#import <Bolts/BFTaskCompletionSource.h>
|
||||
|
||||
#if __has_include(<Bolts/BFAppLink.h>) && TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
|
||||
#import <Bolts/BFAppLink.h>
|
||||
#import <Bolts/BFAppLinkNavigation.h>
|
||||
#import <Bolts/BFAppLinkResolving.h>
|
||||
#import <Bolts/BFAppLinkReturnToRefererController.h>
|
||||
#import <Bolts/BFAppLinkReturnToRefererView.h>
|
||||
#import <Bolts/BFAppLinkTarget.h>
|
||||
#import <Bolts/BFMeasurementEvent.h>
|
||||
#import <Bolts/BFURL.h>
|
||||
#import <Bolts/BFWebViewAppLinkResolver.h>
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface Bolts : NSObject
|
||||
|
||||
/*!
|
||||
Returns the version of the Bolts Framework as an NSString.
|
||||
@returns The NSString representation of the current version.
|
||||
*/
|
||||
+ (NSString *)version;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
Generated
+19
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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 "Bolts.h"
|
||||
|
||||
@implementation Bolts
|
||||
|
||||
+ (NSString *)version {
|
||||
return BOLTS_VERSION;
|
||||
}
|
||||
|
||||
@end
|
||||
+1
@@ -0,0 +1 @@
|
||||
#define BOLTS_VERSION @"1.6.0"
|
||||
Generated
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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>
|
||||
|
||||
/*! The version of the App Link protocol that this library supports */
|
||||
FOUNDATION_EXPORT NSString *const BFAppLinkVersion;
|
||||
|
||||
/*!
|
||||
Contains App Link metadata relevant for navigation on this device
|
||||
derived from the HTML at a given URL.
|
||||
*/
|
||||
@interface BFAppLink : NSObject
|
||||
|
||||
/*!
|
||||
Creates a BFAppLink with the given list of BFAppLinkTargets and target URL.
|
||||
|
||||
Generally, this will only be used by implementers of the BFAppLinkResolving protocol,
|
||||
as these implementers will produce App Link metadata for a given URL.
|
||||
|
||||
@param sourceURL the URL from which this App Link is derived
|
||||
@param targets an ordered list of BFAppLinkTargets for this platform derived
|
||||
from App Link metadata.
|
||||
@param webURL the fallback web URL, if any, for the app link.
|
||||
*/
|
||||
+ (instancetype)appLinkWithSourceURL:(NSURL *)sourceURL
|
||||
targets:(NSArray *)targets
|
||||
webURL:(NSURL *)webURL;
|
||||
|
||||
/*! The URL from which this BFAppLink was derived */
|
||||
@property (nonatomic, strong, readonly) NSURL *sourceURL;
|
||||
|
||||
/*!
|
||||
The ordered list of targets applicable to this platform that will be used
|
||||
for navigation.
|
||||
*/
|
||||
@property (nonatomic, copy, readonly) NSArray *targets;
|
||||
|
||||
/*! The fallback web URL to use if no targets are installed on this device. */
|
||||
@property (nonatomic, strong, readonly) NSURL *webURL;
|
||||
|
||||
@end
|
||||
Generated
+62
@@ -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 "BFAppLink_Internal.h"
|
||||
|
||||
NSString *const BFAppLinkDataParameterName = @"al_applink_data";
|
||||
NSString *const BFAppLinkTargetKeyName = @"target_url";
|
||||
NSString *const BFAppLinkUserAgentKeyName = @"user_agent";
|
||||
NSString *const BFAppLinkExtrasKeyName = @"extras";
|
||||
NSString *const BFAppLinkRefererAppLink = @"referer_app_link";
|
||||
NSString *const BFAppLinkRefererAppName = @"app_name";
|
||||
NSString *const BFAppLinkRefererUrl = @"url";
|
||||
NSString *const BFAppLinkVersionKeyName = @"version";
|
||||
NSString *const BFAppLinkVersion = @"1.0";
|
||||
|
||||
@interface BFAppLink ()
|
||||
|
||||
@property (nonatomic, strong, readwrite) NSURL *sourceURL;
|
||||
@property (nonatomic, copy, readwrite) NSArray *targets;
|
||||
@property (nonatomic, strong, readwrite) NSURL *webURL;
|
||||
|
||||
@property (nonatomic, assign, readwrite, getter=isBackToReferrer) BOOL backToReferrer;
|
||||
|
||||
@end
|
||||
|
||||
@implementation BFAppLink
|
||||
|
||||
+ (instancetype)appLinkWithSourceURL:(NSURL *)sourceURL
|
||||
targets:(NSArray *)targets
|
||||
webURL:(NSURL *)webURL
|
||||
isBackToReferrer:(BOOL)isBackToReferrer {
|
||||
BFAppLink *link = [[self alloc] initWithIsBackToReferrer:isBackToReferrer];
|
||||
link.sourceURL = sourceURL;
|
||||
link.targets = [targets copy];
|
||||
link.webURL = webURL;
|
||||
return link;
|
||||
}
|
||||
|
||||
+ (instancetype)appLinkWithSourceURL:(NSURL *)sourceURL
|
||||
targets:(NSArray *)targets
|
||||
webURL:(NSURL *)webURL {
|
||||
return [self appLinkWithSourceURL:sourceURL
|
||||
targets:targets
|
||||
webURL:webURL
|
||||
isBackToReferrer:NO];
|
||||
}
|
||||
|
||||
- (BFAppLink *)initWithIsBackToReferrer:(BOOL)backToReferrer {
|
||||
if ((self = [super init])) {
|
||||
_backToReferrer = backToReferrer;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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 <Bolts/BFAppLink.h>
|
||||
|
||||
/*!
|
||||
The result of calling navigate on a BFAppLinkNavigation
|
||||
*/
|
||||
typedef NS_ENUM(NSInteger, BFAppLinkNavigationType) {
|
||||
/*! Indicates that the navigation failed and no app was opened */
|
||||
BFAppLinkNavigationTypeFailure,
|
||||
/*! Indicates that the navigation succeeded by opening the URL in the browser */
|
||||
BFAppLinkNavigationTypeBrowser,
|
||||
/*! Indicates that the navigation succeeded by opening the URL in an app on the device */
|
||||
BFAppLinkNavigationTypeApp
|
||||
};
|
||||
|
||||
@protocol BFAppLinkResolving;
|
||||
@class BFTask;
|
||||
|
||||
/*!
|
||||
Represents a pending request to navigate to an App Link. Most developers will
|
||||
simply use navigateToURLInBackground: to open a URL, but developers can build
|
||||
custom requests with additional navigation and app data attached to them by
|
||||
creating BFAppLinkNavigations themselves.
|
||||
*/
|
||||
@interface BFAppLinkNavigation : NSObject
|
||||
|
||||
/*!
|
||||
The extras for the AppLinkNavigation. This will generally contain application-specific
|
||||
data that should be passed along with the request, such as advertiser or affiliate IDs or
|
||||
other such metadata relevant on this device.
|
||||
*/
|
||||
@property (nonatomic, copy, readonly) NSDictionary *extras;
|
||||
|
||||
/*!
|
||||
The al_applink_data for the AppLinkNavigation. This will generally contain data common to
|
||||
navigation attempts such as back-links, user agents, and other information that may be used
|
||||
in routing and handling an App Link request.
|
||||
*/
|
||||
@property (nonatomic, copy, readonly) NSDictionary *appLinkData;
|
||||
|
||||
/*! The AppLink to navigate to */
|
||||
@property (nonatomic, strong, readonly) BFAppLink *appLink;
|
||||
|
||||
/*! Creates an AppLinkNavigation with the given link, extras, and App Link data */
|
||||
+ (instancetype)navigationWithAppLink:(BFAppLink *)appLink
|
||||
extras:(NSDictionary *)extras
|
||||
appLinkData:(NSDictionary *)appLinkData;
|
||||
|
||||
/*! Performs the navigation */
|
||||
- (BFAppLinkNavigationType)navigate:(NSError **)error;
|
||||
|
||||
/*! Returns a BFAppLink for the given URL */
|
||||
+ (BFTask *)resolveAppLinkInBackground:(NSURL *)destination;
|
||||
|
||||
/*! Returns a BFAppLink for the given URL using the given App Link resolution strategy */
|
||||
+ (BFTask *)resolveAppLinkInBackground:(NSURL *)destination resolver:(id<BFAppLinkResolving>)resolver;
|
||||
|
||||
/*! Navigates to a BFAppLink and returns whether it opened in-app or in-browser */
|
||||
+ (BFAppLinkNavigationType)navigateToAppLink:(BFAppLink *)link error:(NSError **)error;
|
||||
|
||||
/*! Navigates to a URL (an asynchronous action) and returns a BFNavigationType */
|
||||
+ (BFTask *)navigateToURLInBackground:(NSURL *)destination;
|
||||
|
||||
/*!
|
||||
Navigates to a URL (an asynchronous action) using the given App Link resolution
|
||||
strategy and returns a BFNavigationType
|
||||
*/
|
||||
+ (BFTask *)navigateToURLInBackground:(NSURL *)destination resolver:(id<BFAppLinkResolving>)resolver;
|
||||
|
||||
/*!
|
||||
Gets the default resolver to be used for App Link resolution. If the developer has not set one explicitly,
|
||||
a basic, built-in resolver will be used.
|
||||
*/
|
||||
+ (id<BFAppLinkResolving>)defaultResolver;
|
||||
|
||||
/*!
|
||||
Sets the default resolver to be used for App Link resolution. Setting this to nil will revert the
|
||||
default resolver to the basic, built-in resolver provided by Bolts.
|
||||
*/
|
||||
+ (void)setDefaultResolver:(id<BFAppLinkResolving>)resolver;
|
||||
|
||||
@end
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* 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 <UIKit/UIKit.h>
|
||||
|
||||
#import "BFAppLinkNavigation.h"
|
||||
#import "BFTaskCompletionSource.h"
|
||||
#import "BFAppLinkTarget.h"
|
||||
#import "BoltsVersion.h"
|
||||
#import "BFWebViewAppLinkResolver.h"
|
||||
#import "BFExecutor.h"
|
||||
#import "BFTask.h"
|
||||
#import "BFMeasurementEvent_Internal.h"
|
||||
#import "BFAppLink_Internal.h"
|
||||
|
||||
FOUNDATION_EXPORT NSString *const BFAppLinkDataParameterName;
|
||||
FOUNDATION_EXPORT NSString *const BFAppLinkTargetKeyName;
|
||||
FOUNDATION_EXPORT NSString *const BFAppLinkUserAgentKeyName;
|
||||
FOUNDATION_EXPORT NSString *const BFAppLinkExtrasKeyName;
|
||||
FOUNDATION_EXPORT NSString *const BFAppLinkVersionKeyName;
|
||||
|
||||
static id<BFAppLinkResolving> defaultResolver;
|
||||
|
||||
@interface BFAppLinkNavigation ()
|
||||
|
||||
@property (nonatomic, copy, readwrite) NSDictionary *extras;
|
||||
@property (nonatomic, copy, readwrite) NSDictionary *appLinkData;
|
||||
@property (nonatomic, strong, readwrite) BFAppLink *appLink;
|
||||
|
||||
@end
|
||||
|
||||
@implementation BFAppLinkNavigation
|
||||
|
||||
+ (instancetype)navigationWithAppLink:(BFAppLink *)appLink
|
||||
extras:(NSDictionary *)extras
|
||||
appLinkData:(NSDictionary *)appLinkData {
|
||||
BFAppLinkNavigation *navigation = [[self alloc] init];
|
||||
navigation.appLink = appLink;
|
||||
navigation.extras = extras;
|
||||
navigation.appLinkData = appLinkData;
|
||||
return navigation;
|
||||
}
|
||||
|
||||
- (NSString *)stringByEscapingQueryString:(NSString *)string {
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0 || __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_9
|
||||
return [string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
|
||||
#else
|
||||
return (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL,
|
||||
(CFStringRef)string,
|
||||
NULL,
|
||||
(CFStringRef) @":/?#[]@!$&'()*+,;=",
|
||||
kCFStringEncodingUTF8));
|
||||
#endif
|
||||
}
|
||||
|
||||
- (NSURL *)appLinkURLWithTargetURL:(NSURL *)targetUrl error:(NSError **)error {
|
||||
NSMutableDictionary *appLinkData = [NSMutableDictionary dictionaryWithDictionary:self.appLinkData ?: @{}];
|
||||
|
||||
// Add applink protocol data
|
||||
if (!appLinkData[BFAppLinkUserAgentKeyName]) {
|
||||
appLinkData[BFAppLinkUserAgentKeyName] = [NSString stringWithFormat:@"Bolts iOS %@", BOLTS_VERSION];
|
||||
}
|
||||
if (!appLinkData[BFAppLinkVersionKeyName]) {
|
||||
appLinkData[BFAppLinkVersionKeyName] = BFAppLinkVersion;
|
||||
}
|
||||
appLinkData[BFAppLinkTargetKeyName] = [self.appLink.sourceURL absoluteString];
|
||||
appLinkData[BFAppLinkExtrasKeyName] = self.extras ?: @{};
|
||||
|
||||
// JSON-ify the applink data
|
||||
NSError *jsonError = nil;
|
||||
NSData *jsonBlob = [NSJSONSerialization dataWithJSONObject:appLinkData options:0 error:&jsonError];
|
||||
if (!jsonError) {
|
||||
NSString *jsonString = [[NSString alloc] initWithData:jsonBlob encoding:NSUTF8StringEncoding];
|
||||
NSString *encoded = [self stringByEscapingQueryString:jsonString];
|
||||
|
||||
NSString *endUrlString = [NSString stringWithFormat:@"%@%@%@=%@",
|
||||
[targetUrl absoluteString],
|
||||
targetUrl.query ? @"&" : @"?",
|
||||
BFAppLinkDataParameterName,
|
||||
encoded];
|
||||
|
||||
return [NSURL URLWithString:endUrlString];
|
||||
} else {
|
||||
if (error) {
|
||||
*error = jsonError;
|
||||
}
|
||||
|
||||
// If there was an error encoding the app link data, fail hard.
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (BFAppLinkNavigationType)navigate:(NSError **)error {
|
||||
NSURL *openedURL = nil;
|
||||
NSError *encodingError = nil;
|
||||
BFAppLinkNavigationType retType = BFAppLinkNavigationTypeFailure;
|
||||
|
||||
// Find the first eligible/launchable target in the BFAppLink.
|
||||
for (BFAppLinkTarget *target in self.appLink.targets) {
|
||||
NSURL *appLinkAppURL = [self appLinkURLWithTargetURL:target.URL error:&encodingError];
|
||||
if (encodingError || !appLinkAppURL) {
|
||||
if (error) {
|
||||
*error = encodingError;
|
||||
}
|
||||
} else if ([[UIApplication sharedApplication] openURL:appLinkAppURL]) {
|
||||
retType = BFAppLinkNavigationTypeApp;
|
||||
openedURL = appLinkAppURL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!openedURL && self.appLink.webURL) {
|
||||
// Fall back to opening the url in the browser if available.
|
||||
NSURL *appLinkBrowserURL = [self appLinkURLWithTargetURL:self.appLink.webURL error:&encodingError];
|
||||
if (encodingError || !appLinkBrowserURL) {
|
||||
// If there was an error encoding the app link data, fail hard.
|
||||
if (error) {
|
||||
*error = encodingError;
|
||||
}
|
||||
} else if ([[UIApplication sharedApplication] openURL:appLinkBrowserURL]) {
|
||||
// This was a browser navigation.
|
||||
retType = BFAppLinkNavigationTypeBrowser;
|
||||
openedURL = appLinkBrowserURL;
|
||||
}
|
||||
}
|
||||
|
||||
[self postAppLinkNavigateEventNotificationWithTargetURL:openedURL
|
||||
error:error ? *error : nil
|
||||
type:retType];
|
||||
return retType;
|
||||
}
|
||||
|
||||
- (void)postAppLinkNavigateEventNotificationWithTargetURL:(NSURL *)outputURL error:(NSError *)error type:(BFAppLinkNavigationType)type {
|
||||
NSString *const EVENT_YES_VAL = @"1";
|
||||
NSString *const EVENT_NO_VAL = @"0";
|
||||
NSMutableDictionary *logData = [[NSMutableDictionary alloc] init];
|
||||
|
||||
NSString *outputURLScheme = [outputURL scheme];
|
||||
NSString *outputURLString = [outputURL absoluteString];
|
||||
if (outputURLScheme) {
|
||||
logData[@"outputURLScheme"] = outputURLScheme;
|
||||
}
|
||||
if (outputURLString) {
|
||||
logData[@"outputURL"] = outputURLString;
|
||||
}
|
||||
|
||||
NSString *sourceURLString = [self.appLink.sourceURL absoluteString];
|
||||
NSString *sourceURLHost = [self.appLink.sourceURL host];
|
||||
NSString *sourceURLScheme = [self.appLink.sourceURL scheme];
|
||||
if (sourceURLString) {
|
||||
logData[@"sourceURL"] = sourceURLString;
|
||||
}
|
||||
if (sourceURLHost) {
|
||||
logData[@"sourceHost"] = sourceURLHost;
|
||||
}
|
||||
if (sourceURLScheme) {
|
||||
logData[@"sourceScheme"] = sourceURLScheme;
|
||||
}
|
||||
if ([error localizedDescription]) {
|
||||
logData[@"error"] = [error localizedDescription];
|
||||
}
|
||||
NSString *success = nil; //no
|
||||
NSString *linkType = nil; // unknown;
|
||||
switch (type) {
|
||||
case BFAppLinkNavigationTypeFailure:
|
||||
success = EVENT_NO_VAL;
|
||||
linkType = @"fail";
|
||||
break;
|
||||
case BFAppLinkNavigationTypeBrowser:
|
||||
success = EVENT_YES_VAL;
|
||||
linkType = @"web";
|
||||
break;
|
||||
case BFAppLinkNavigationTypeApp:
|
||||
success = EVENT_YES_VAL;
|
||||
linkType = @"app";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (success) {
|
||||
logData[@"success"] = success;
|
||||
}
|
||||
if (linkType) {
|
||||
logData[@"type"] = linkType;
|
||||
}
|
||||
|
||||
if ([self.appLink isBackToReferrer]) {
|
||||
[BFMeasurementEvent postNotificationForEventName:BFAppLinkNavigateBackToReferrerEventName args:logData];
|
||||
} else {
|
||||
[BFMeasurementEvent postNotificationForEventName:BFAppLinkNavigateOutEventName args:logData];
|
||||
}
|
||||
}
|
||||
|
||||
+ (BFTask *)resolveAppLinkInBackground:(NSURL *)destination resolver:(id<BFAppLinkResolving>)resolver {
|
||||
return [resolver appLinkFromURLInBackground:destination];
|
||||
}
|
||||
|
||||
+ (BFTask *)resolveAppLinkInBackground:(NSURL *)destination {
|
||||
return [self resolveAppLinkInBackground:destination resolver:[self defaultResolver]];
|
||||
}
|
||||
|
||||
+ (BFTask *)navigateToURLInBackground:(NSURL *)destination {
|
||||
return [self navigateToURLInBackground:destination
|
||||
resolver:[self defaultResolver]];
|
||||
}
|
||||
|
||||
+ (BFTask *)navigateToURLInBackground:(NSURL *)destination
|
||||
resolver:(id<BFAppLinkResolving>)resolver {
|
||||
BFTask *resolutionTask = [self resolveAppLinkInBackground:destination
|
||||
resolver:resolver];
|
||||
return [resolutionTask continueWithExecutor:[BFExecutor mainThreadExecutor]
|
||||
withSuccessBlock:^id(BFTask *task) {
|
||||
NSError *error = nil;
|
||||
BFAppLinkNavigationType result = [self navigateToAppLink:task.result
|
||||
error:&error];
|
||||
if (error) {
|
||||
return [BFTask taskWithError:error];
|
||||
} else {
|
||||
return @(result);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
+ (BFAppLinkNavigationType)navigateToAppLink:(BFAppLink *)link error:(NSError **)error {
|
||||
return [[BFAppLinkNavigation navigationWithAppLink:link
|
||||
extras:nil
|
||||
appLinkData:nil] navigate:error];
|
||||
}
|
||||
|
||||
+ (id<BFAppLinkResolving>)defaultResolver {
|
||||
if (defaultResolver) {
|
||||
return defaultResolver;
|
||||
}
|
||||
return [BFWebViewAppLinkResolver sharedInstance];
|
||||
}
|
||||
|
||||
+ (void)setDefaultResolver:(id<BFAppLinkResolving>)resolver {
|
||||
defaultResolver = resolver;
|
||||
}
|
||||
|
||||
@end
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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>
|
||||
|
||||
@class BFTask;
|
||||
|
||||
/*!
|
||||
Implement this protocol to provide an alternate strategy for resolving
|
||||
App Links that may include pre-fetching, caching, or querying for App Link
|
||||
data from an index provided by a service provider.
|
||||
*/
|
||||
@protocol BFAppLinkResolving <NSObject>
|
||||
|
||||
/*!
|
||||
Asynchronously resolves App Link data for a given URL.
|
||||
|
||||
@param url The URL to resolve into an App Link.
|
||||
@returns A BFTask that will return a BFAppLink for the given URL.
|
||||
*/
|
||||
- (BFTask *)appLinkFromURLInBackground:(NSURL *)url;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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 <UIKit/UIKit.h>
|
||||
|
||||
#import <Bolts/BFAppLinkReturnToRefererView.h>
|
||||
|
||||
@class BFAppLink;
|
||||
@class BFAppLinkReturnToRefererController;
|
||||
|
||||
/*!
|
||||
Protocol that a class can implement in order to be notified when the user has navigated back
|
||||
to the referer of an App Link.
|
||||
*/
|
||||
@protocol BFAppLinkReturnToRefererControllerDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
|
||||
/*! Called when the user has tapped to navigate, but before the navigation has been performed. */
|
||||
- (void)returnToRefererController:(BFAppLinkReturnToRefererController *)controller
|
||||
willNavigateToAppLink:(BFAppLink *)appLink;
|
||||
|
||||
/*! Called after the navigation has been attempted, with an indication of whether the referer
|
||||
app link was successfully opened. */
|
||||
- (void)returnToRefererController:(BFAppLinkReturnToRefererController *)controller
|
||||
didNavigateToAppLink:(BFAppLink *)url
|
||||
type:(BFAppLinkNavigationType)type;
|
||||
|
||||
@end
|
||||
|
||||
/*!
|
||||
A controller class that implements default behavior for a BFAppLinkReturnToRefererView, including
|
||||
the ability to display the view above the navigation bar for navigation-based apps.
|
||||
*/
|
||||
@interface BFAppLinkReturnToRefererController : NSObject <BFAppLinkReturnToRefererViewDelegate>
|
||||
|
||||
/*!
|
||||
The delegate that will be notified when the user navigates back to the referer.
|
||||
*/
|
||||
@property (nonatomic, weak) id<BFAppLinkReturnToRefererControllerDelegate> delegate;
|
||||
|
||||
/*!
|
||||
The BFAppLinkReturnToRefererView this controller is controlling.
|
||||
*/
|
||||
@property (nonatomic, strong) BFAppLinkReturnToRefererView *view;
|
||||
|
||||
/*!
|
||||
Initializes a controller suitable for controlling a BFAppLinkReturnToRefererView that is to be displayed
|
||||
contained within another UIView (i.e., not displayed above the navigation bar).
|
||||
*/
|
||||
- (instancetype)init;
|
||||
|
||||
/*!
|
||||
Initializes a controller suitable for controlling a BFAppLinkReturnToRefererView that is to be displayed
|
||||
displayed above the navigation bar.
|
||||
*/
|
||||
- (instancetype)initForDisplayAboveNavController:(UINavigationController *)navController;
|
||||
|
||||
/*!
|
||||
Removes the view entirely from the navigation controller it is currently displayed in.
|
||||
*/
|
||||
- (void)removeFromNavController;
|
||||
|
||||
/*!
|
||||
Shows the BFAppLinkReturnToRefererView with the specified referer information. If nil or missing data,
|
||||
the view will not be displayed. */
|
||||
- (void)showViewForRefererAppLink:(BFAppLink *)refererAppLink;
|
||||
|
||||
/*!
|
||||
Shows the BFAppLinkReturnToRefererView with referer information extracted from the specified URL.
|
||||
If nil or missing referer App Link data, the view will not be displayed. */
|
||||
- (void)showViewForRefererURL:(NSURL *)url;
|
||||
|
||||
/*!
|
||||
Closes the view, possibly animating it.
|
||||
*/
|
||||
- (void)closeViewAnimated:(BOOL)animated;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* 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 "BFAppLinkReturnToRefererController.h"
|
||||
|
||||
#import "BFAppLink.h"
|
||||
#import "BFAppLinkReturnToRefererView_Internal.h"
|
||||
#import "BFURL_Internal.h"
|
||||
|
||||
static const CFTimeInterval kBFViewAnimationDuration = 0.25f;
|
||||
|
||||
@implementation BFAppLinkReturnToRefererController {
|
||||
UINavigationController *_navigationController;
|
||||
BFAppLinkReturnToRefererView *_view;
|
||||
}
|
||||
|
||||
#pragma mark - Object lifecycle
|
||||
|
||||
- (instancetype)init {
|
||||
return [self initForDisplayAboveNavController:nil];
|
||||
}
|
||||
|
||||
- (instancetype)initForDisplayAboveNavController:(UINavigationController *)navController {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_navigationController = navController;
|
||||
|
||||
if (_navigationController != nil) {
|
||||
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
|
||||
[nc addObserver:self
|
||||
selector:@selector(statusBarFrameWillChange:)
|
||||
name:UIApplicationWillChangeStatusBarFrameNotification
|
||||
object:nil];
|
||||
[nc addObserver:self
|
||||
selector:@selector(statusBarFrameDidChange:)
|
||||
name:UIApplicationDidChangeStatusBarFrameNotification
|
||||
object:nil];
|
||||
[nc addObserver:self
|
||||
selector:@selector(orientationDidChange:)
|
||||
name:UIDeviceOrientationDidChangeNotification
|
||||
object:nil];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
_view.delegate = nil;
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
#pragma mark - Public API
|
||||
|
||||
- (BFAppLinkReturnToRefererView *)view {
|
||||
if (!_view) {
|
||||
self.view = [[BFAppLinkReturnToRefererView alloc] initWithFrame:CGRectZero];
|
||||
if (_navigationController) {
|
||||
[_navigationController.view addSubview:_view];
|
||||
}
|
||||
}
|
||||
return _view;
|
||||
}
|
||||
|
||||
- (void)setView:(BFAppLinkReturnToRefererView *)view {
|
||||
if (_view != view) {
|
||||
_view.delegate = nil;
|
||||
}
|
||||
|
||||
_view = view;
|
||||
_view.delegate = self;
|
||||
|
||||
if (_navigationController) {
|
||||
_view.includeStatusBarInSize = BFIncludeStatusBarInSizeAlways;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showViewForRefererAppLink:(BFAppLink *)refererAppLink {
|
||||
self.view.refererAppLink = refererAppLink;
|
||||
|
||||
[_view sizeToFit];
|
||||
|
||||
if (_navigationController) {
|
||||
if (!_view.closed) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self moveNavigationBar];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showViewForRefererURL:(NSURL *)url {
|
||||
BFAppLink *appLink = [BFURL URLForRenderBackToReferrerBarURL:url].appLinkReferer;
|
||||
[self showViewForRefererAppLink:appLink];
|
||||
}
|
||||
|
||||
- (void)removeFromNavController {
|
||||
if (_navigationController) {
|
||||
[_view removeFromSuperview];
|
||||
_navigationController = nil;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - BFAppLinkReturnToRefererViewDelegate
|
||||
|
||||
- (void)returnToRefererViewDidTapInsideCloseButton:(BFAppLinkReturnToRefererView *)view {
|
||||
[self closeViewAnimated:YES explicitlyClosed:YES];
|
||||
}
|
||||
|
||||
- (void)returnToRefererViewDidTapInsideLink:(BFAppLinkReturnToRefererView *)view
|
||||
link:(BFAppLink *)link {
|
||||
[self openRefererAppLink:link];
|
||||
[self closeViewAnimated:NO explicitlyClosed:NO];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)statusBarFrameWillChange:(NSNotification *)notification {
|
||||
NSValue *rectValue = [[notification userInfo] valueForKey:UIApplicationStatusBarFrameUserInfoKey];
|
||||
CGRect newFrame;
|
||||
[rectValue getValue:&newFrame];
|
||||
|
||||
if (_navigationController && !_view.closed) {
|
||||
if (CGRectGetHeight(newFrame) == 40) {
|
||||
UIViewAnimationOptions options = UIViewAnimationOptionBeginFromCurrentState;
|
||||
[UIView animateWithDuration:kBFViewAnimationDuration delay:0.0 options:options animations:^{
|
||||
_view.frame = CGRectMake(0.0, 0.0, CGRectGetWidth(_view.bounds), 0.0);
|
||||
} completion:nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)statusBarFrameDidChange:(NSNotification *)notification {
|
||||
NSValue *rectValue = [[notification userInfo] valueForKey:UIApplicationStatusBarFrameUserInfoKey];
|
||||
CGRect newFrame;
|
||||
[rectValue getValue:&newFrame];
|
||||
|
||||
if (_navigationController && !_view.closed) {
|
||||
if (CGRectGetHeight(newFrame) == 40) {
|
||||
UIViewAnimationOptions options = UIViewAnimationOptionBeginFromCurrentState;
|
||||
[UIView animateWithDuration:kBFViewAnimationDuration delay:0.0 options:options animations:^{
|
||||
[_view sizeToFit];
|
||||
[self moveNavigationBar];
|
||||
} completion:nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)orientationDidChange:(NSNotificationCenter *)notification {
|
||||
if (_navigationController && !_view.closed && CGRectGetHeight(_view.bounds) > 0) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self moveNavigationBar];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)moveNavigationBar {
|
||||
if (_view.closed || !_view.refererAppLink) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self updateNavigationBarY:CGRectGetHeight(_view.bounds)];
|
||||
}
|
||||
|
||||
- (void)updateNavigationBarY:(CGFloat)y {
|
||||
UINavigationBar *navigationBar = _navigationController.navigationBar;
|
||||
CGRect navigationBarFrame = navigationBar.frame;
|
||||
CGFloat oldContainerViewY = CGRectGetMaxY(navigationBarFrame);
|
||||
navigationBarFrame.origin.y = y;
|
||||
navigationBar.frame = navigationBarFrame;
|
||||
|
||||
CGFloat dy = CGRectGetMaxY(navigationBarFrame) - oldContainerViewY;
|
||||
UIView *containerView = _navigationController.visibleViewController.view.superview;
|
||||
containerView.frame = UIEdgeInsetsInsetRect(containerView.frame, UIEdgeInsetsMake(dy, 0.0, 0.0, 0.0));
|
||||
}
|
||||
|
||||
- (void)closeViewAnimated:(BOOL)animated {
|
||||
[self closeViewAnimated:animated explicitlyClosed:YES];
|
||||
}
|
||||
|
||||
- (void)closeViewAnimated:(BOOL)animated explicitlyClosed:(BOOL)explicitlyClosed {
|
||||
void (^closer)(void) = ^{
|
||||
if (_navigationController) {
|
||||
[self updateNavigationBarY:_view.statusBarHeight];
|
||||
}
|
||||
|
||||
CGRect frame = _view.frame;
|
||||
frame.size.height = 0.0;
|
||||
_view.frame = frame;
|
||||
};
|
||||
|
||||
if (animated) {
|
||||
[UIView animateWithDuration:kBFViewAnimationDuration animations:^{
|
||||
closer();
|
||||
} completion:^(BOOL finished) {
|
||||
if (explicitlyClosed) {
|
||||
_view.closed = YES;
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
closer();
|
||||
if (explicitlyClosed) {
|
||||
_view.closed = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)openRefererAppLink:(BFAppLink *)refererAppLink {
|
||||
if (refererAppLink) {
|
||||
id<BFAppLinkReturnToRefererControllerDelegate> delegate = _delegate;
|
||||
if ([delegate respondsToSelector:@selector(returnToRefererController:willNavigateToAppLink:)]) {
|
||||
[delegate returnToRefererController:self willNavigateToAppLink:refererAppLink];
|
||||
}
|
||||
|
||||
NSError *error = nil;
|
||||
BFAppLinkNavigationType type = [BFAppLinkNavigation navigateToAppLink:refererAppLink error:&error];
|
||||
|
||||
if ([delegate respondsToSelector:@selector(returnToRefererController:didNavigateToAppLink:type:)]) {
|
||||
[delegate returnToRefererController:self didNavigateToAppLink:refererAppLink type:type];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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 <UIKit/UIKit.h>
|
||||
|
||||
#import <Bolts/BFAppLinkNavigation.h>
|
||||
|
||||
@class BFAppLinkReturnToRefererView;
|
||||
@class BFURL;
|
||||
|
||||
typedef NS_ENUM(NSUInteger, BFIncludeStatusBarInSize) {
|
||||
BFIncludeStatusBarInSizeNever,
|
||||
BFIncludeStatusBarInSizeIOS7AndLater,
|
||||
BFIncludeStatusBarInSizeAlways,
|
||||
};
|
||||
|
||||
/*!
|
||||
Protocol that a class can implement in order to be notified when the user has navigated back
|
||||
to the referer of an App Link.
|
||||
*/
|
||||
@protocol BFAppLinkReturnToRefererViewDelegate <NSObject>
|
||||
|
||||
/*!
|
||||
Called when the user has tapped inside the close button.
|
||||
*/
|
||||
- (void)returnToRefererViewDidTapInsideCloseButton:(BFAppLinkReturnToRefererView *)view;
|
||||
|
||||
/*!
|
||||
Called when the user has tapped inside the App Link portion of the view.
|
||||
*/
|
||||
- (void)returnToRefererViewDidTapInsideLink:(BFAppLinkReturnToRefererView *)view
|
||||
link:(BFAppLink *)link;
|
||||
|
||||
@end
|
||||
|
||||
/*!
|
||||
Provides a UIView that displays a button allowing users to navigate back to the
|
||||
application that launched the App Link currently being handled, if the App Link
|
||||
contained referer data. The user can also close the view by clicking a close button
|
||||
rather than navigating away. If the view is provided an App Link that does not contain
|
||||
referer data, it will have zero size and no UI will be displayed.
|
||||
*/
|
||||
@interface BFAppLinkReturnToRefererView : UIView
|
||||
|
||||
/*!
|
||||
The delegate that will be notified when the user navigates back to the referer.
|
||||
*/
|
||||
@property (nonatomic, weak) id<BFAppLinkReturnToRefererViewDelegate> delegate;
|
||||
|
||||
/*!
|
||||
The color of the text label and close button.
|
||||
*/
|
||||
@property (nonatomic, strong) UIColor *textColor;
|
||||
|
||||
@property (nonatomic, strong) BFAppLink *refererAppLink;
|
||||
|
||||
/*!
|
||||
Indicates whether to extend the size of the view to include the current status bar
|
||||
size, for use in scenarios where the view might extend under the status bar on iOS 7 and
|
||||
above; this property has no effect on earlier versions of iOS.
|
||||
*/
|
||||
@property (nonatomic, assign) BFIncludeStatusBarInSize includeStatusBarInSize;
|
||||
|
||||
/*!
|
||||
Indicates whether the user has closed the view by clicking the close button.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL closed;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,269 @@
|
||||
/*
|
||||
* 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 "BFAppLinkReturnToRefererView.h"
|
||||
|
||||
#import "BFAppLink.h"
|
||||
#import "BFAppLinkTarget.h"
|
||||
|
||||
static const CGFloat BFMarginX = 8.5f;
|
||||
static const CGFloat BFMarginY = 8.5f;
|
||||
|
||||
static NSString *const BFRefererAppLink = @"referer_app_link";
|
||||
static NSString *const BFRefererAppName = @"app_name";
|
||||
static NSString *const BFRefererUrl = @"url";
|
||||
static const CGFloat BFCloseButtonWidth = 12.0;
|
||||
static const CGFloat BFCloseButtonHeight = 12.0;
|
||||
|
||||
@interface BFAppLinkReturnToRefererView ()
|
||||
|
||||
@property (nonatomic, strong) UILabel *labelView;
|
||||
@property (nonatomic, strong) UIButton *closeButton;
|
||||
@property (nonatomic, strong) UITapGestureRecognizer *insideTapGestureRecognizer;
|
||||
@property (nonatomic, strong) UIView *viewToMoveWithNavController;
|
||||
|
||||
@end
|
||||
|
||||
@implementation BFAppLinkReturnToRefererView {
|
||||
BOOL _explicitlyHidden;
|
||||
}
|
||||
|
||||
#pragma mark - Initialization
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self commonInit];
|
||||
[self sizeToFit];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self) {
|
||||
[self commonInit];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)commonInit {
|
||||
// Initialization code
|
||||
_includeStatusBarInSize = BFIncludeStatusBarInSizeIOS7AndLater;
|
||||
|
||||
// iOS 7 system blue color
|
||||
self.backgroundColor = [UIColor colorWithRed:0.0f green:122.0f / 255.0f blue:1.0f alpha:1.0f];
|
||||
self.textColor = [UIColor whiteColor];
|
||||
self.clipsToBounds = YES;
|
||||
|
||||
[self initViews];
|
||||
}
|
||||
|
||||
- (void)initViews {
|
||||
if (!_labelView && !_closeButton) {
|
||||
_closeButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
_closeButton.backgroundColor = [UIColor clearColor];
|
||||
_closeButton.userInteractionEnabled = YES;
|
||||
_closeButton.clipsToBounds = YES;
|
||||
_closeButton.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin;
|
||||
_closeButton.contentMode = UIViewContentModeCenter;
|
||||
[_closeButton addTarget:self action:@selector(closeButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
[self addSubview:_closeButton];
|
||||
|
||||
_labelView = [[UILabel alloc] initWithFrame:CGRectZero];
|
||||
_labelView.font = [UIFont systemFontOfSize:[UIFont smallSystemFontSize]];
|
||||
_labelView.textColor = [UIColor whiteColor];
|
||||
_labelView.backgroundColor = [UIColor clearColor];
|
||||
#ifdef __IPHONE_6_0
|
||||
_labelView.textAlignment = NSTextAlignmentCenter;
|
||||
#else
|
||||
_labelView.textAlignment = UITextAlignmentCenter;
|
||||
#endif
|
||||
_labelView.clipsToBounds = YES;
|
||||
[self updateLabelText];
|
||||
[self addSubview:_labelView];
|
||||
|
||||
_insideTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTapInside:)];
|
||||
_labelView.userInteractionEnabled = YES;
|
||||
[_labelView addGestureRecognizer:_insideTapGestureRecognizer];
|
||||
|
||||
[self updateColors];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Layout
|
||||
|
||||
- (CGSize)intrinsicContentSize {
|
||||
CGSize size = self.bounds.size;
|
||||
if (_closed || !self.hasRefererData) {
|
||||
size.height = 0.0;
|
||||
} else {
|
||||
CGSize labelSize = [_labelView sizeThatFits:size];
|
||||
size = CGSizeMake(size.width, labelSize.height + 2 * BFMarginY + self.statusBarHeight);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
|
||||
CGRect bounds = self.bounds;
|
||||
|
||||
_labelView.preferredMaxLayoutWidth = _labelView.bounds.size.width;
|
||||
CGSize labelSize = [_labelView sizeThatFits:bounds.size];
|
||||
_labelView.frame = CGRectMake(BFMarginX,
|
||||
CGRectGetMaxY(bounds) - labelSize.height - 1.5f * BFMarginY,
|
||||
CGRectGetMaxX(bounds) - BFCloseButtonWidth - 3 * BFMarginX,
|
||||
labelSize.height + BFMarginY);
|
||||
|
||||
_closeButton.frame = CGRectMake(CGRectGetMaxX(bounds) - BFCloseButtonWidth - 2 * BFMarginX,
|
||||
_labelView.center.y - BFCloseButtonHeight / 2.0f - BFMarginY,
|
||||
BFCloseButtonWidth + 2 * BFMarginX,
|
||||
BFCloseButtonHeight + 2 * BFMarginY);
|
||||
}
|
||||
|
||||
- (CGSize)sizeThatFits:(CGSize)size {
|
||||
if (_closed || !self.hasRefererData) {
|
||||
size = CGSizeMake(size.width, 0.0);
|
||||
} else {
|
||||
CGSize labelSize = [_labelView sizeThatFits:size];
|
||||
size = CGSizeMake(size.width, labelSize.height + 2 * BFMarginY + self.statusBarHeight);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
- (CGFloat)statusBarHeight {
|
||||
UIApplication *application = [UIApplication sharedApplication];
|
||||
|
||||
BOOL include;
|
||||
switch (_includeStatusBarInSize) {
|
||||
case BFIncludeStatusBarInSizeAlways:
|
||||
include = YES;
|
||||
break;
|
||||
case BFIncludeStatusBarInSizeIOS7AndLater: {
|
||||
float systemVersion = [[[UIDevice currentDevice] systemVersion] floatValue];
|
||||
include = (systemVersion >= 7.0);
|
||||
break;
|
||||
}
|
||||
case BFIncludeStatusBarInSizeNever:
|
||||
include = NO;
|
||||
break;
|
||||
}
|
||||
if (include && !application.statusBarHidden) {
|
||||
BOOL landscape = UIInterfaceOrientationIsLandscape(application.statusBarOrientation);
|
||||
CGRect statusBarFrame = application.statusBarFrame;
|
||||
return landscape ? CGRectGetWidth(statusBarFrame) : CGRectGetHeight(statusBarFrame);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#pragma mark - Public API
|
||||
|
||||
- (void)setIncludeStatusBarInSize:(BFIncludeStatusBarInSize)includeStatusBarInSize {
|
||||
_includeStatusBarInSize = includeStatusBarInSize;
|
||||
[self setNeedsLayout];
|
||||
[self invalidateIntrinsicContentSize];
|
||||
}
|
||||
|
||||
- (void)setTextColor:(UIColor *)textColor {
|
||||
_textColor = textColor;
|
||||
[self updateColors];
|
||||
}
|
||||
|
||||
- (void)setRefererAppLink:(BFAppLink *)refererAppLink {
|
||||
_refererAppLink = refererAppLink;
|
||||
[self updateLabelText];
|
||||
[self updateHidden];
|
||||
[self invalidateIntrinsicContentSize];
|
||||
}
|
||||
|
||||
- (void)setClosed:(BOOL)closed {
|
||||
if (_closed != closed) {
|
||||
_closed = closed;
|
||||
[self updateHidden];
|
||||
[self invalidateIntrinsicContentSize];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setHidden:(BOOL)hidden {
|
||||
_explicitlyHidden = hidden;
|
||||
[self updateHidden];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)updateLabelText {
|
||||
NSString *appName = (_refererAppLink && _refererAppLink.targets[0]) ? [_refererAppLink.targets[0] appName] : nil;
|
||||
_labelView.text = [self localizedLabelForReferer:appName];
|
||||
}
|
||||
|
||||
- (void)updateColors {
|
||||
UIImage *closeButtonImage = [self drawCloseButtonImageWithColor:_textColor];
|
||||
|
||||
_labelView.textColor = _textColor;
|
||||
[_closeButton setImage:closeButtonImage forState:UIControlStateNormal];
|
||||
}
|
||||
|
||||
- (UIImage *)drawCloseButtonImageWithColor:(UIColor *)color {
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(CGSizeMake(BFCloseButtonWidth, BFCloseButtonHeight), NO, 0.0f);
|
||||
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
|
||||
CGContextSetStrokeColorWithColor(context, [color CGColor]);
|
||||
CGContextSetFillColorWithColor(context, [color CGColor]);
|
||||
|
||||
CGContextSetLineWidth(context, 1.25f);
|
||||
|
||||
CGFloat inset = 0.5f;
|
||||
|
||||
CGContextMoveToPoint(context, inset, inset);
|
||||
CGContextAddLineToPoint(context, BFCloseButtonWidth - inset, BFCloseButtonHeight - inset);
|
||||
CGContextStrokePath(context);
|
||||
|
||||
CGContextMoveToPoint(context, BFCloseButtonWidth - inset, inset);
|
||||
CGContextAddLineToPoint(context, inset, BFCloseButtonHeight - inset);
|
||||
CGContextStrokePath(context);
|
||||
|
||||
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSString *)localizedLabelForReferer:(NSString *)refererName {
|
||||
if (!refererName) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString *format = NSLocalizedString(@"Touch to return to %1$@", @"Format for the string to return to a calling app.");
|
||||
|
||||
return [NSString stringWithFormat:format, refererName];
|
||||
}
|
||||
|
||||
- (BOOL)hasRefererData {
|
||||
return _refererAppLink && _refererAppLink.targets[0];
|
||||
}
|
||||
|
||||
- (void)closeButtonTapped:(id)sender {
|
||||
[_delegate returnToRefererViewDidTapInsideCloseButton:self];
|
||||
}
|
||||
|
||||
- (void)onTapInside:(UIGestureRecognizer *)sender {
|
||||
[_delegate returnToRefererViewDidTapInsideLink:self link:_refererAppLink];
|
||||
}
|
||||
|
||||
- (void)updateHidden {
|
||||
[super setHidden:_explicitlyHidden || _closed || !self.hasRefererData];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -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 <Bolts/BFAppLinkReturnToRefererView.h>
|
||||
|
||||
@interface BFAppLinkReturnToRefererView (Internal)
|
||||
|
||||
- (CGFloat)statusBarHeight;
|
||||
|
||||
@end
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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>
|
||||
|
||||
/*!
|
||||
Represents a target defined in App Link metadata, consisting of at least
|
||||
a URL, and optionally an App Store ID and name.
|
||||
*/
|
||||
@interface BFAppLinkTarget : NSObject
|
||||
|
||||
/*! Creates a BFAppLinkTarget with the given app site and target URL. */
|
||||
+ (instancetype)appLinkTargetWithURL:(NSURL *)url
|
||||
appStoreId:(NSString *)appStoreId
|
||||
appName:(NSString *)appName;
|
||||
|
||||
/*! The URL prefix for this app link target */
|
||||
@property (nonatomic, strong, readonly) NSURL *URL;
|
||||
|
||||
/*! The app ID for the app store */
|
||||
@property (nonatomic, copy, readonly) NSString *appStoreId;
|
||||
|
||||
/*! The name of the app */
|
||||
@property (nonatomic, copy, readonly) NSString *appName;
|
||||
|
||||
@end
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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 "BFAppLinkTarget.h"
|
||||
|
||||
@interface BFAppLinkTarget ()
|
||||
|
||||
@property (nonatomic, strong, readwrite) NSURL *URL;
|
||||
@property (nonatomic, copy, readwrite) NSString *appStoreId;
|
||||
@property (nonatomic, copy, readwrite) NSString *appName;
|
||||
|
||||
@end
|
||||
|
||||
@implementation BFAppLinkTarget
|
||||
|
||||
+ (instancetype)appLinkTargetWithURL:(NSURL *)url
|
||||
appStoreId:(NSString *)appStoreId
|
||||
appName:(NSString *)appName {
|
||||
BFAppLinkTarget *target = [[self alloc] init];
|
||||
target.URL = url;
|
||||
target.appStoreId = appStoreId;
|
||||
target.appName = appName;
|
||||
return target;
|
||||
}
|
||||
|
||||
@end
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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 <Bolts/BFAppLink.h>
|
||||
|
||||
FOUNDATION_EXPORT NSString *const BFAppLinkDataParameterName;
|
||||
FOUNDATION_EXPORT NSString *const BFAppLinkTargetKeyName;
|
||||
FOUNDATION_EXPORT NSString *const BFAppLinkUserAgentKeyName;
|
||||
FOUNDATION_EXPORT NSString *const BFAppLinkExtrasKeyName;
|
||||
FOUNDATION_EXPORT NSString *const BFAppLinkVersionKeyName;
|
||||
FOUNDATION_EXPORT NSString *const BFAppLinkRefererAppLink;
|
||||
FOUNDATION_EXPORT NSString *const BFAppLinkRefererAppName;
|
||||
FOUNDATION_EXPORT NSString *const BFAppLinkRefererUrl;
|
||||
|
||||
@interface BFAppLink (Internal)
|
||||
|
||||
+ (instancetype)appLinkWithSourceURL:(NSURL *)sourceURL
|
||||
targets:(NSArray *)targets
|
||||
webURL:(NSURL *)webURL
|
||||
isBackToReferrer:(BOOL)isBackToReferrer;
|
||||
|
||||
/*! return if this AppLink is to go back to referrer. */
|
||||
@property (nonatomic, assign, readonly, getter=isBackToReferrer) BOOL backToReferrer;
|
||||
|
||||
@end
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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>
|
||||
|
||||
/*! The name of the notification posted by BFMeasurementEvent */
|
||||
FOUNDATION_EXPORT NSString *const BFMeasurementEventNotificationName;
|
||||
|
||||
/*! Defines keys in the userInfo object for the notification named BFMeasurementEventNotificationName */
|
||||
/*! The string field for the name of the event */
|
||||
FOUNDATION_EXPORT NSString *const BFMeasurementEventNameKey;
|
||||
/*! The dictionary field for the arguments of the event */
|
||||
FOUNDATION_EXPORT NSString *const BFMeasurementEventArgsKey;
|
||||
|
||||
/*! Bolts Events raised by BFMeasurementEvent for Applink */
|
||||
/*!
|
||||
The name of the event posted when [BFURL URLWithURL:] is called successfully. This represents the successful parsing of an app link URL.
|
||||
*/
|
||||
FOUNDATION_EXPORT NSString *const BFAppLinkParseEventName;
|
||||
|
||||
/*!
|
||||
The name of the event posted when [BFURL URLWithInboundURL:] is called successfully.
|
||||
This represents parsing an inbound app link URL from a different application
|
||||
*/
|
||||
FOUNDATION_EXPORT NSString *const BFAppLinkNavigateInEventName;
|
||||
|
||||
/*! The event raised when the user navigates from your app to other apps */
|
||||
FOUNDATION_EXPORT NSString *const BFAppLinkNavigateOutEventName;
|
||||
|
||||
/*!
|
||||
The event raised when the user navigates out from your app and back to the referrer app.
|
||||
e.g when the user leaves your app after tapping the back-to-referrer navigation bar
|
||||
*/
|
||||
FOUNDATION_EXPORT NSString *const BFAppLinkNavigateBackToReferrerEventName;
|
||||
|
||||
@interface BFMeasurementEvent : NSObject
|
||||
|
||||
@end
|
||||
+62
@@ -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 "BFMeasurementEvent_Internal.h"
|
||||
|
||||
NSString *const BFMeasurementEventNotificationName = @"com.parse.bolts.measurement_event";
|
||||
|
||||
NSString *const BFMeasurementEventNameKey = @"event_name";
|
||||
NSString *const BFMeasurementEventArgsKey = @"event_args";
|
||||
|
||||
/* app Link Event raised by this BFURL */
|
||||
NSString *const BFAppLinkParseEventName = @"al_link_parse";
|
||||
NSString *const BFAppLinkNavigateInEventName = @"al_nav_in";
|
||||
|
||||
/*! AppLink events raised in this class */
|
||||
NSString *const BFAppLinkNavigateOutEventName = @"al_nav_out";
|
||||
NSString *const BFAppLinkNavigateBackToReferrerEventName = @"al_ref_back_out";
|
||||
|
||||
__attribute__((noinline)) void warnOnMissingEventName() {
|
||||
NSLog(@"Warning: Missing event name when logging bolts measurement event. \n"
|
||||
" Ignoring this event in logging.");
|
||||
}
|
||||
|
||||
@implementation BFMeasurementEvent {
|
||||
NSString *_name;
|
||||
NSDictionary *_args;
|
||||
}
|
||||
|
||||
- (void)postNotification {
|
||||
if (!_name) {
|
||||
warnOnMissingEventName();
|
||||
return;
|
||||
}
|
||||
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
|
||||
NSDictionary *userInfo = @{BFMeasurementEventNameKey : _name,
|
||||
BFMeasurementEventArgsKey : _args};
|
||||
|
||||
[center postNotificationName:BFMeasurementEventNotificationName
|
||||
object:self
|
||||
userInfo:userInfo];
|
||||
}
|
||||
|
||||
- (instancetype)initEventWithName:(NSString *)name args:(NSDictionary *)args {
|
||||
if ((self = [super init])) {
|
||||
_name = name;
|
||||
_args = args ? args : @{};
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (void)postNotificationForEventName:(NSString *)name args:(NSDictionary *)args {
|
||||
[[[self alloc] initEventWithName:name args:args] postNotification];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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 <Bolts/BFMeasurementEvent.h>
|
||||
/*!
|
||||
Provides methods for posting notifications from the Bolts framework
|
||||
*/
|
||||
@interface BFMeasurementEvent (Internal)
|
||||
|
||||
+ (void)postNotificationForEventName:(NSString *)name args:(NSDictionary *)args;
|
||||
|
||||
@end
|
||||
Generated
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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>
|
||||
|
||||
@class BFAppLink;
|
||||
|
||||
/*!
|
||||
Provides a set of utilities for working with NSURLs, such as parsing of query parameters
|
||||
and handling for App Link requests.
|
||||
*/
|
||||
@interface BFURL : NSObject
|
||||
|
||||
/*!
|
||||
Creates a link target from a raw URL.
|
||||
On success, this posts the BFAppLinkParseEventName measurement event. If you are constructing the BFURL within your application delegate's
|
||||
application:openURL:sourceApplication:annotation:, you should instead use URLWithInboundURL:sourceApplication:
|
||||
to support better BFMeasurementEvent notifications
|
||||
@param url The instance of `NSURL` to create BFURL from.
|
||||
*/
|
||||
+ (BFURL *)URLWithURL:(NSURL *)url;
|
||||
|
||||
/*!
|
||||
Creates a link target from a raw URL received from an external application. This is typically called from the app delegate's
|
||||
application:openURL:sourceApplication:annotation: and will post the BFAppLinkNavigateInEventName measurement event.
|
||||
@param url The instance of `NSURL` to create BFURL from.
|
||||
@param sourceApplication the bundle ID of the app that is requesting your app to open the URL. The same sourceApplication in application:openURL:sourceApplication:annotation:
|
||||
*/
|
||||
+ (BFURL *)URLWithInboundURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication;
|
||||
|
||||
/*!
|
||||
Gets the target URL. If the link is an App Link, this is the target of the App Link.
|
||||
Otherwise, it is the url that created the target.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) NSURL *targetURL;
|
||||
|
||||
/*!
|
||||
Gets the query parameters for the target, parsed into an NSDictionary.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) NSDictionary *targetQueryParameters;
|
||||
|
||||
/*!
|
||||
If this link target is an App Link, this is the data found in al_applink_data.
|
||||
Otherwise, it is nil.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) NSDictionary *appLinkData;
|
||||
|
||||
/*!
|
||||
If this link target is an App Link, this is the data found in extras.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) NSDictionary *appLinkExtras;
|
||||
|
||||
/*!
|
||||
The App Link indicating how to navigate back to the referer app, if any.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) BFAppLink *appLinkReferer;
|
||||
|
||||
/*!
|
||||
The URL that was used to create this BFURL.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) NSURL *inputURL;
|
||||
|
||||
/*!
|
||||
The query parameters of the inputURL, parsed into an NSDictionary.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) NSDictionary *inputQueryParameters;
|
||||
|
||||
@end
|
||||
Generated
+142
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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 "BFURL_Internal.h"
|
||||
#import "BFAppLink_Internal.h"
|
||||
#import "BFAppLinkTarget.h"
|
||||
#import "BFMeasurementEvent_Internal.h"
|
||||
|
||||
@implementation BFURL
|
||||
|
||||
- (instancetype)initWithURL:(NSURL *)url forOpenInboundURL:(BOOL)forOpenURLEvent sourceApplication:(NSString *)sourceApplication forRenderBackToReferrerBar:(BOOL)forRenderBackToReferrerBar {
|
||||
self = [super init];
|
||||
if (!self) return nil;
|
||||
|
||||
_inputURL = url;
|
||||
_targetURL = url;
|
||||
|
||||
// Parse the query string parameters for the base URL
|
||||
NSDictionary *baseQuery = [BFURL queryParametersForURL:url];
|
||||
_inputQueryParameters = baseQuery;
|
||||
_targetQueryParameters = baseQuery;
|
||||
|
||||
// Check for applink_data
|
||||
NSString *appLinkDataString = baseQuery[BFAppLinkDataParameterName];
|
||||
if (appLinkDataString) {
|
||||
// Try to parse the JSON
|
||||
NSError *error = nil;
|
||||
NSDictionary *applinkData = [NSJSONSerialization JSONObjectWithData:[appLinkDataString dataUsingEncoding:NSUTF8StringEncoding]
|
||||
options:0
|
||||
error:&error];
|
||||
if (!error && [applinkData isKindOfClass:[NSDictionary class]]) {
|
||||
// If the version is not specified, assume it is 1.
|
||||
NSString *version = applinkData[BFAppLinkVersionKeyName] ?: @"1.0";
|
||||
NSString *target = applinkData[BFAppLinkTargetKeyName];
|
||||
if ([version isKindOfClass:[NSString class]] &&
|
||||
[version isEqual:BFAppLinkVersion]) {
|
||||
// There's applink data! The target should actually be the applink target.
|
||||
_appLinkData = applinkData;
|
||||
id applinkExtras = applinkData[BFAppLinkExtrasKeyName];
|
||||
if (applinkExtras && [applinkExtras isKindOfClass:[NSDictionary class]]) {
|
||||
_appLinkExtras = applinkExtras;
|
||||
}
|
||||
_targetURL = ([target isKindOfClass:[NSString class]] ? [NSURL URLWithString:target] : url);
|
||||
_targetQueryParameters = [BFURL queryParametersForURL:_targetURL];
|
||||
|
||||
NSDictionary *refererAppLink = _appLinkData[BFAppLinkRefererAppLink];
|
||||
NSString *refererURLString = refererAppLink[BFAppLinkRefererUrl];
|
||||
NSString *refererAppName = refererAppLink[BFAppLinkRefererAppName];
|
||||
|
||||
if (refererURLString && refererAppName) {
|
||||
BFAppLinkTarget *target = [BFAppLinkTarget appLinkTargetWithURL:[NSURL URLWithString:refererURLString]
|
||||
appStoreId:nil
|
||||
appName:refererAppName];
|
||||
_appLinkReferer = [BFAppLink appLinkWithSourceURL:[NSURL URLWithString:refererURLString]
|
||||
targets:@[ target ]
|
||||
webURL:nil
|
||||
isBackToReferrer:YES];
|
||||
}
|
||||
|
||||
// Raise Measurement Event
|
||||
NSString *const EVENT_YES_VAL = @"1";
|
||||
NSString *const EVENT_NO_VAL = @"0";
|
||||
NSMutableDictionary *logData = [[NSMutableDictionary alloc] init];
|
||||
logData[@"version"] = version;
|
||||
if (refererURLString) {
|
||||
logData[@"refererURL"] = refererURLString;
|
||||
}
|
||||
if (refererAppName) {
|
||||
logData[@"refererAppName"] = refererAppName;
|
||||
}
|
||||
if (sourceApplication) {
|
||||
logData[@"sourceApplication"] = sourceApplication;
|
||||
}
|
||||
if ([_targetURL absoluteString]) {
|
||||
logData[@"targetURL"] = [_targetURL absoluteString];
|
||||
}
|
||||
if ([_inputURL absoluteString]) {
|
||||
logData[@"inputURL"] = [_inputURL absoluteString];
|
||||
}
|
||||
if ([_inputURL scheme]) {
|
||||
logData[@"inputURLScheme"] = [_inputURL scheme];
|
||||
}
|
||||
logData[@"forRenderBackToReferrerBar"] = forRenderBackToReferrerBar ? EVENT_YES_VAL : EVENT_NO_VAL;
|
||||
logData[@"forOpenUrl"] = forOpenURLEvent ? EVENT_YES_VAL : EVENT_NO_VAL;
|
||||
[BFMeasurementEvent postNotificationForEventName:BFAppLinkParseEventName args:logData];
|
||||
if (forOpenURLEvent) {
|
||||
[BFMeasurementEvent postNotificationForEventName:BFAppLinkNavigateInEventName args:logData];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (BFURL *)URLWithURL:(NSURL *)url {
|
||||
return [[BFURL alloc] initWithURL:url forOpenInboundURL:NO sourceApplication:nil forRenderBackToReferrerBar:NO];
|
||||
}
|
||||
|
||||
+ (BFURL *)URLWithInboundURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication {
|
||||
return [[BFURL alloc] initWithURL:url forOpenInboundURL:YES sourceApplication:sourceApplication forRenderBackToReferrerBar:NO];
|
||||
}
|
||||
|
||||
+ (BFURL *)URLForRenderBackToReferrerBarURL:(NSURL *)url {
|
||||
return [[BFURL alloc] initWithURL:url forOpenInboundURL:NO sourceApplication:nil forRenderBackToReferrerBar:YES];
|
||||
}
|
||||
|
||||
+ (NSString *)decodeURLString:(NSString *)string {
|
||||
return (NSString *)CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapes(NULL,
|
||||
(CFStringRef)string,
|
||||
CFSTR("")));
|
||||
}
|
||||
|
||||
+ (NSDictionary *)queryParametersForURL:(NSURL *)url {
|
||||
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
|
||||
NSString *query = url.query;
|
||||
if ([query isEqualToString:@""]) {
|
||||
return @{};
|
||||
}
|
||||
NSArray *queryComponents = [query componentsSeparatedByString:@"&"];
|
||||
for (NSString *component in queryComponents) {
|
||||
NSRange equalsLocation = [component rangeOfString:@"="];
|
||||
if (equalsLocation.location == NSNotFound) {
|
||||
// There's no equals, so associate the key with NSNull
|
||||
parameters[[self decodeURLString:component]] = [NSNull null];
|
||||
} else {
|
||||
NSString *key = [self decodeURLString:[component substringToIndex:equalsLocation.location]];
|
||||
NSString *value = [self decodeURLString:[component substringFromIndex:equalsLocation.location + 1]];
|
||||
parameters[key] = value;
|
||||
}
|
||||
}
|
||||
return [NSDictionary dictionaryWithDictionary:parameters];
|
||||
}
|
||||
|
||||
@end
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* 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 <Bolts/BFURL.h>
|
||||
|
||||
@interface BFURL (Internal)
|
||||
+ (BFURL *)URLForRenderBackToReferrerBarURL:(NSURL *)url;
|
||||
@end
|
||||
@@ -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 <Foundation/Foundation.h>
|
||||
|
||||
#import <Bolts/BFAppLinkResolving.h>
|
||||
|
||||
/*!
|
||||
A reference implementation for an App Link resolver that uses a hidden UIWebView
|
||||
to parse the HTML containing App Link metadata.
|
||||
*/
|
||||
@interface BFWebViewAppLinkResolver : NSObject <BFAppLinkResolving>
|
||||
|
||||
/*!
|
||||
Gets the instance of a BFWebViewAppLinkResolver.
|
||||
*/
|
||||
+ (instancetype)sharedInstance;
|
||||
|
||||
@end
|
||||
+302
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
* 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 <UIKit/UIKit.h>
|
||||
|
||||
#import "BFWebViewAppLinkResolver.h"
|
||||
#import "BFAppLink.h"
|
||||
#import "BFAppLinkTarget.h"
|
||||
#import "BFTask.h"
|
||||
#import "BFTaskCompletionSource.h"
|
||||
#import "BFExecutor.h"
|
||||
|
||||
// Defines JavaScript to extract app link tags from HTML content
|
||||
static NSString *const BFWebViewAppLinkResolverTagExtractionJavaScript = @""
|
||||
"(function() {"
|
||||
" var metaTags = document.getElementsByTagName('meta');"
|
||||
" var results = [];"
|
||||
" for (var i = 0; i < metaTags.length; i++) {"
|
||||
" var property = metaTags[i].getAttribute('property');"
|
||||
" if (property && property.substring(0, 'al:'.length) === 'al:') {"
|
||||
" var tag = { \"property\": metaTags[i].getAttribute('property') };"
|
||||
" if (metaTags[i].hasAttribute('content')) {"
|
||||
" tag['content'] = metaTags[i].getAttribute('content');"
|
||||
" }"
|
||||
" results.push(tag);"
|
||||
" }"
|
||||
" }"
|
||||
" return JSON.stringify(results);"
|
||||
"})()";
|
||||
static NSString *const BFWebViewAppLinkResolverIOSURLKey = @"url";
|
||||
static NSString *const BFWebViewAppLinkResolverIOSAppStoreIdKey = @"app_store_id";
|
||||
static NSString *const BFWebViewAppLinkResolverIOSAppNameKey = @"app_name";
|
||||
static NSString *const BFWebViewAppLinkResolverDictionaryValueKey = @"_value";
|
||||
static NSString *const BFWebViewAppLinkResolverPreferHeader = @"Prefer-Html-Meta-Tags";
|
||||
static NSString *const BFWebViewAppLinkResolverMetaTagPrefix = @"al";
|
||||
static NSString *const BFWebViewAppLinkResolverWebKey = @"web";
|
||||
static NSString *const BFWebViewAppLinkResolverIOSKey = @"ios";
|
||||
static NSString *const BFWebViewAppLinkResolverIPhoneKey = @"iphone";
|
||||
static NSString *const BFWebViewAppLinkResolverIPadKey = @"ipad";
|
||||
static NSString *const BFWebViewAppLinkResolverWebURLKey = @"url";
|
||||
static NSString *const BFWebViewAppLinkResolverShouldFallbackKey = @"should_fallback";
|
||||
|
||||
@interface BFWebViewAppLinkResolverWebViewDelegate : NSObject <UIWebViewDelegate>
|
||||
|
||||
@property (nonatomic, copy) void (^didFinishLoad)(UIWebView *webView);
|
||||
@property (nonatomic, copy) void (^didFailLoadWithError)(UIWebView *webView, NSError *error);
|
||||
@property (nonatomic, assign) BOOL hasLoaded;
|
||||
|
||||
@end
|
||||
|
||||
@implementation BFWebViewAppLinkResolverWebViewDelegate
|
||||
|
||||
- (void)webViewDidFinishLoad:(UIWebView *)webView {
|
||||
if (self.didFinishLoad) {
|
||||
self.didFinishLoad(webView);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)webViewDidStartLoad:(UIWebView *)webView {
|
||||
}
|
||||
|
||||
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
|
||||
if (self.didFailLoadWithError) {
|
||||
self.didFailLoadWithError(webView, error);
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
|
||||
if (self.hasLoaded) {
|
||||
// Consider loading a second resource to be "success", since it indicates an inner frame
|
||||
// or redirect is happening. We can run the tag extraction script at this point.
|
||||
self.didFinishLoad(webView);
|
||||
return NO;
|
||||
}
|
||||
self.hasLoaded = YES;
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation BFWebViewAppLinkResolver
|
||||
|
||||
+ (instancetype)sharedInstance {
|
||||
static id instance;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [[self alloc] init];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
- (BFTask *)followRedirects:(NSURL *)url {
|
||||
// This task will be resolved with either the redirect NSURL
|
||||
// or a dictionary with the response data to be returned.
|
||||
BFTaskCompletionSource *tcs = [BFTaskCompletionSource taskCompletionSource];
|
||||
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
|
||||
[request setValue:BFWebViewAppLinkResolverMetaTagPrefix forHTTPHeaderField:BFWebViewAppLinkResolverPreferHeader];
|
||||
|
||||
void (^completion)(NSURLResponse *response, NSData *data, NSError *error) = ^(NSURLResponse *response, NSData *data, NSError *error) {
|
||||
if (error) {
|
||||
[tcs setError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
|
||||
|
||||
// NSURLConnection usually follows redirects automatically, but the
|
||||
// documentation is unclear what the default is. This helps it along.
|
||||
if (httpResponse.statusCode >= 300 && httpResponse.statusCode < 400) {
|
||||
NSString *redirectString = httpResponse.allHeaderFields[@"Location"];
|
||||
NSURL *redirectURL = [NSURL URLWithString:redirectString];
|
||||
[tcs setResult:redirectURL];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
[tcs setResult:@{ @"response" : response, @"data" : data }];
|
||||
};
|
||||
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0 || __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_9
|
||||
NSURLSession *session = [NSURLSession sharedSession];
|
||||
[[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
completion(response, data, error);
|
||||
}] resume];
|
||||
#else
|
||||
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:completion];
|
||||
#endif
|
||||
|
||||
return [tcs.task continueWithSuccessBlock:^id(BFTask *task) {
|
||||
// If we redirected, just keep recursing.
|
||||
if ([task.result isKindOfClass:[NSURL class]]) {
|
||||
return [self followRedirects:task.result];
|
||||
}
|
||||
return task;
|
||||
}];
|
||||
}
|
||||
|
||||
- (BFTask *)appLinkFromURLInBackground:(NSURL *)url {
|
||||
return [[self followRedirects:url] continueWithExecutor:[BFExecutor mainThreadExecutor]
|
||||
withSuccessBlock:^id(BFTask *task) {
|
||||
NSData *responseData = task.result[@"data"];
|
||||
NSHTTPURLResponse *response = task.result[@"response"];
|
||||
BFTaskCompletionSource *tcs = [BFTaskCompletionSource taskCompletionSource];
|
||||
|
||||
UIWebView *webView = [[UIWebView alloc] init];
|
||||
BFWebViewAppLinkResolverWebViewDelegate *listener = [[BFWebViewAppLinkResolverWebViewDelegate alloc] init];
|
||||
__block BFWebViewAppLinkResolverWebViewDelegate *retainedListener = listener;
|
||||
listener.didFinishLoad = ^(UIWebView *view) {
|
||||
if (retainedListener) {
|
||||
NSDictionary *ogData = [self getALDataFromLoadedPage:view];
|
||||
[view removeFromSuperview];
|
||||
view.delegate = nil;
|
||||
retainedListener = nil;
|
||||
[tcs setResult:[self appLinkFromALData:ogData destination:url]];
|
||||
}
|
||||
};
|
||||
listener.didFailLoadWithError = ^(UIWebView* view, NSError *error) {
|
||||
if (retainedListener) {
|
||||
[view removeFromSuperview];
|
||||
view.delegate = nil;
|
||||
retainedListener = nil;
|
||||
[tcs setError:error];
|
||||
}
|
||||
};
|
||||
webView.delegate = listener;
|
||||
webView.hidden = YES;
|
||||
[webView loadData:responseData
|
||||
MIMEType:response.MIMEType
|
||||
textEncodingName:response.textEncodingName
|
||||
baseURL:response.URL];
|
||||
UIWindow *window = [UIApplication sharedApplication].windows.firstObject;
|
||||
[window addSubview:webView];
|
||||
|
||||
return tcs.task;
|
||||
}];
|
||||
}
|
||||
|
||||
/*
|
||||
Builds up a data structure filled with the app link data from the meta tags on a page.
|
||||
The structure of this object is a dictionary where each key holds an array of app link
|
||||
data dictionaries. Values are stored in a key called "_value".
|
||||
*/
|
||||
- (NSDictionary *)parseALData:(NSArray *)dataArray {
|
||||
NSMutableDictionary *al = [NSMutableDictionary dictionary];
|
||||
for (NSDictionary *tag in dataArray) {
|
||||
NSString *name = tag[@"property"];
|
||||
if (![name isKindOfClass:[NSString class]]) {
|
||||
continue;
|
||||
}
|
||||
NSArray *nameComponents = [name componentsSeparatedByString:@":"];
|
||||
if (![nameComponents[0] isEqualToString:BFWebViewAppLinkResolverMetaTagPrefix]) {
|
||||
continue;
|
||||
}
|
||||
NSMutableDictionary *root = al;
|
||||
for (int i = 1; i < nameComponents.count; i++) {
|
||||
NSMutableArray *children = root[nameComponents[i]];
|
||||
if (!children) {
|
||||
children = [NSMutableArray array];
|
||||
root[nameComponents[i]] = children;
|
||||
}
|
||||
NSMutableDictionary *child = children.lastObject;
|
||||
if (!child || i == nameComponents.count - 1) {
|
||||
child = [NSMutableDictionary dictionary];
|
||||
[children addObject:child];
|
||||
}
|
||||
root = child;
|
||||
}
|
||||
if (tag[@"content"]) {
|
||||
root[BFWebViewAppLinkResolverDictionaryValueKey] = tag[@"content"];
|
||||
}
|
||||
}
|
||||
return al;
|
||||
}
|
||||
|
||||
- (NSDictionary *)getALDataFromLoadedPage:(UIWebView *)webView {
|
||||
// Run some JavaScript in the webview to fetch the meta tags.
|
||||
NSString *jsonString = [webView stringByEvaluatingJavaScriptFromString:BFWebViewAppLinkResolverTagExtractionJavaScript];
|
||||
NSError *error = nil;
|
||||
NSArray *arr = [NSJSONSerialization JSONObjectWithData:[jsonString dataUsingEncoding:NSUTF8StringEncoding]
|
||||
options:0
|
||||
error:&error];
|
||||
return [self parseALData:arr];
|
||||
}
|
||||
|
||||
/*
|
||||
Converts app link data into a BFAppLink containing the targets relevant for this platform.
|
||||
*/
|
||||
- (BFAppLink *)appLinkFromALData:(NSDictionary *)appLinkDict destination:(NSURL *)destination {
|
||||
NSMutableArray *linkTargets = [NSMutableArray array];
|
||||
|
||||
NSArray *platformData = nil;
|
||||
switch (UI_USER_INTERFACE_IDIOM()) {
|
||||
case UIUserInterfaceIdiomPad:
|
||||
platformData = @[ appLinkDict[BFWebViewAppLinkResolverIPadKey] ?: @{},
|
||||
appLinkDict[BFWebViewAppLinkResolverIOSKey] ?: @{} ];
|
||||
break;
|
||||
case UIUserInterfaceIdiomPhone:
|
||||
platformData = @[ appLinkDict[BFWebViewAppLinkResolverIPhoneKey] ?: @{},
|
||||
appLinkDict[BFWebViewAppLinkResolverIOSKey] ?: @{} ];
|
||||
break;
|
||||
#ifdef __TVOS_9_0
|
||||
case UIUserInterfaceIdiomTV:
|
||||
#endif
|
||||
case UIUserInterfaceIdiomUnspecified:
|
||||
default:
|
||||
// Future-proofing. Other User Interface idioms should only hit ios.
|
||||
platformData = @[ appLinkDict[BFWebViewAppLinkResolverIOSKey] ?: @{} ];
|
||||
break;
|
||||
}
|
||||
|
||||
for (NSArray *platformObjects in platformData) {
|
||||
for (NSDictionary *platformDict in platformObjects) {
|
||||
// The schema requires a single url/app store id/app name,
|
||||
// but we could find multiple of them. We'll make a best effort
|
||||
// to interpret this data.
|
||||
NSArray *urls = platformDict[BFWebViewAppLinkResolverIOSURLKey];
|
||||
NSArray *appStoreIds = platformDict[BFWebViewAppLinkResolverIOSAppStoreIdKey];
|
||||
NSArray *appNames = platformDict[BFWebViewAppLinkResolverIOSAppNameKey];
|
||||
|
||||
NSUInteger maxCount = MAX(urls.count, MAX(appStoreIds.count, appNames.count));
|
||||
|
||||
for (NSUInteger i = 0; i < maxCount; i++) {
|
||||
NSString *urlString = urls[i][BFWebViewAppLinkResolverDictionaryValueKey];
|
||||
NSURL *url = urlString ? [NSURL URLWithString:urlString] : nil;
|
||||
NSString *appStoreId = appStoreIds[i][BFWebViewAppLinkResolverDictionaryValueKey];
|
||||
NSString *appName = appNames[i][BFWebViewAppLinkResolverDictionaryValueKey];
|
||||
BFAppLinkTarget *target = [BFAppLinkTarget appLinkTargetWithURL:url
|
||||
appStoreId:appStoreId
|
||||
appName:appName];
|
||||
[linkTargets addObject:target];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NSDictionary *webDict = appLinkDict[BFWebViewAppLinkResolverWebKey][0];
|
||||
NSString *webUrlString = webDict[BFWebViewAppLinkResolverWebURLKey][0][BFWebViewAppLinkResolverDictionaryValueKey];
|
||||
NSString *shouldFallbackString = webDict[BFWebViewAppLinkResolverShouldFallbackKey][0][BFWebViewAppLinkResolverDictionaryValueKey];
|
||||
|
||||
NSURL *webUrl = destination;
|
||||
|
||||
if (shouldFallbackString &&
|
||||
[@[ @"no", @"false", @"0" ] containsObject:[shouldFallbackString lowercaseString]]) {
|
||||
webUrl = nil;
|
||||
}
|
||||
if (webUrl && webUrlString) {
|
||||
webUrl = [NSURL URLWithString:webUrlString];
|
||||
}
|
||||
|
||||
return [BFAppLink appLinkWithSourceURL:destination
|
||||
targets:linkTargets
|
||||
webURL:webUrl];
|
||||
}
|
||||
|
||||
@end
|
||||
Generated
+30
@@ -0,0 +1,30 @@
|
||||
BSD License
|
||||
|
||||
For Bolts software
|
||||
|
||||
Copyright (c) 2013-present, Facebook, Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name Facebook nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
Generated
+682
@@ -0,0 +1,682 @@
|
||||
Bolts
|
||||
============
|
||||
[](https://travis-ci.org/BoltsFramework/Bolts-iOS)
|
||||
[](https://codecov.io/github/BoltsFramework/Bolts-iOS?branch=master)
|
||||
[](https://github.com/Carthage/Carthage)
|
||||
[](http://cocoadocs.org/docsets/Bolts/)
|
||||
[](http://cocoadocs.org/docsets/Bolts/)
|
||||
[](https://github.com/BoltsFramework/Bolts-iOS/blob/master/LICENSE)
|
||||
[](https://www.versioneye.com/objective-c/bolts/references)
|
||||
|
||||
Bolts is a collection of low-level libraries designed to make developing mobile
|
||||
apps easier. Bolts was designed by Parse and Facebook for our own internal use,
|
||||
and we have decided to open source these libraries to make them available to
|
||||
others. Using these libraries does not require using any Parse services. Nor
|
||||
do they require having a Parse or Facebook developer account.
|
||||
|
||||
Bolts includes:
|
||||
|
||||
* "Tasks", which make organization of complex asynchronous code more manageable. A task is kind of like a JavaScript Promise, but available for iOS and Android.
|
||||
* An implementation of the [App Links protocol](http://www.applinks.org), helping you link to content in other apps and handle incoming deep-links.
|
||||
|
||||
For more information, see the [Bolts iOS API Reference](http://boltsframework.github.io/docs/ios/).
|
||||
|
||||
# Tasks
|
||||
|
||||
To build a truly responsive iOS application, you must keep long-running operations off of the UI thread, and be careful to avoid blocking anything the UI thread might be waiting on. This means you will need to execute various operations in the background. To make this easier, we've added a class called `BFTask`. A task represents the result of an asynchronous operation. Typically, a `BFTask` is returned from an asynchronous function and gives the ability to continue processing the result of the task. When a task is returned from a function, it's already begun doing its job. A task is not tied to a particular threading model: it represents the work being done, not where it is executing. Tasks have many advantages over other methods of asynchronous programming, such as callbacks. `BFTask` is not a replacement for `NSOperation` or GCD. In fact, they play well together. But tasks do fill in some gaps that those technologies don't address.
|
||||
* `BFTask` takes care of managing dependencies for you. Unlike using `NSOperation` for dependency management, you don't have to declare all dependencies before starting a `BFTask`. For example, imagine you need to save a set of objects and each one may or may not require saving child objects. With an `NSOperation`, you would normally have to create operations for each of the child saves ahead of time. But you don't always know before you start the work whether that's going to be necessary. That can make managing dependencies with `NSOperation` very painful. Even in the best case, you have to create your dependencies before the operations that depend on them, which results in code that appears in a different order than it executes. With `BFTask`, you can decide during your operation's work whether there will be subtasks and return the other task in just those cases.
|
||||
* `BFTasks` release their dependencies. `NSOperation` strongly retains its dependencies, so if you have a queue of ordered operations and sequence them using dependencies, you have a leak, because every operation gets retained forever. `BFTasks` release their callbacks as soon as they are run, so everything cleans up after itself. This can reduce memory use, and simplify memory management.
|
||||
* `BFTasks` keep track of the state of finished tasks: It tracks whether there was a returned value, the task was cancelled, or if an error occurred. It also has convenience methods for propagating errors. With `NSOperation`, you have to build all of this stuff yourself.
|
||||
* `BFTasks` don't depend on any particular threading model. So it's easy to have some tasks perform their work with an operation queue, while others perform work using blocks with GCD. These tasks can depend on each other seamlessly.
|
||||
* Performing several tasks in a row will not create nested "pyramid" code as you would get when using only callbacks.
|
||||
* `BFTasks` are fully composable, allowing you to perform branching, parallelism, and complex error handling, without the spaghetti code of having many named callbacks.
|
||||
* You can arrange task-based code in the order that it executes, rather than having to split your logic across scattered callback functions.
|
||||
|
||||
For the examples in this doc, assume there are async versions of some common Parse methods, called `saveAsync:` and `findAsync:` which return a `Task`. In a later section, we'll show how to define these functions yourself.
|
||||
|
||||
## The `continueWithBlock` Method
|
||||
|
||||
Every `BFTask` has a method named `continueWithBlock:` which takes a continuation block. A continuation is a block that will be executed when the task is complete. You can then inspect the task to check if it was successful and to get its result.
|
||||
|
||||
```objective-c
|
||||
// Objective-C
|
||||
[[self saveAsync:obj] continueWithBlock:^id(BFTask *task) {
|
||||
if (task.isCancelled) {
|
||||
// the save was cancelled.
|
||||
} else if (task.error) {
|
||||
// the save failed.
|
||||
} else {
|
||||
// the object was saved successfully.
|
||||
PFObject *object = task.result;
|
||||
}
|
||||
return nil;
|
||||
}];
|
||||
```
|
||||
|
||||
```swift
|
||||
// Swift
|
||||
self.saveAsync(obj).continueWithBlock {
|
||||
(task: BFTask!) -> BFTask in
|
||||
if task.isCancelled() {
|
||||
// the save was cancelled.
|
||||
} else if task.error() {
|
||||
// the save failed.
|
||||
} else {
|
||||
// the object was saved successfully.
|
||||
var object = task.result() as PFObject
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
BFTasks use Objective-C blocks, so the syntax should be pretty straightforward. Let's look closer at the types involved with an example.
|
||||
|
||||
```objective-c
|
||||
// Objective-C
|
||||
/**
|
||||
* Gets an NSString asynchronously.
|
||||
*/
|
||||
- (BFTask *)getStringAsync {
|
||||
// Let's suppose getNumberAsync returns a BFTask whose result is an NSNumber.
|
||||
return [[self getNumberAsync] continueWithBlock:^id(BFTask *task) {
|
||||
// This continuation block takes the NSNumber BFTask as input,
|
||||
// and provides an NSString as output.
|
||||
|
||||
NSNumber *number = task.result;
|
||||
return [NSString stringWithFormat:@"%@", number];
|
||||
)];
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
// Swift
|
||||
/**
|
||||
* Gets an NSString asynchronously.
|
||||
*/
|
||||
func getStringAsync() -> BFTask {
|
||||
//Let's suppose getNumberAsync returns a BFTask whose result is an NSNumber.
|
||||
return self.getNumberAsync().continueWithBlock {
|
||||
(task: BFTask!) -> NSString in
|
||||
// This continuation block takes the NSNumber BFTask as input,
|
||||
// and provides an NSString as output.
|
||||
|
||||
let number = task.result() as NSNumber
|
||||
return NSString(format:"%@", number)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In many cases, you only want to do more work if the previous task was successful, and propagate any errors or cancellations to be dealt with later. To do this, use the `continueWithSuccessBlock:` method instead of `continueWithBlock:`.
|
||||
|
||||
```objective-c
|
||||
// Objective-C
|
||||
[[self saveAsync:obj] continueWithSuccessBlock:^id(BFTask *task) {
|
||||
// the object was saved successfully.
|
||||
return nil;
|
||||
}];
|
||||
```
|
||||
|
||||
```swift
|
||||
// Swift
|
||||
self.saveAsync(obj).continueWithSuccessBlock {
|
||||
(task: BFTask!) -> AnyObject! in
|
||||
// the object was saved successfully.
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## Chaining Tasks Together
|
||||
|
||||
BFTasks are a little bit magical, in that they let you chain them without nesting. If you return a BFTask from `continueWithBlock:`, then the task returned by `continueWithBlock:` will not be considered finished until the new task returned from the new continuation block. This lets you perform multiple actions without incurring the pyramid code you would get with callbacks. Likewise, you can return a `BFTask` from `continueWithSuccessBlock:`. So, return a `BFTask` to do more asynchronous work.
|
||||
|
||||
```objective-c
|
||||
// Objective-C
|
||||
PFQuery *query = [PFQuery queryWithClassName:@"Student"];
|
||||
[query orderByDescending:@"gpa"];
|
||||
[[[[[self findAsync:query] continueWithSuccessBlock:^id(BFTask *task) {
|
||||
NSArray *students = task.result;
|
||||
PFObject *valedictorian = [students objectAtIndex:0];
|
||||
[valedictorian setObject:@YES forKey:@"valedictorian"];
|
||||
return [self saveAsync:valedictorian];
|
||||
}] continueWithSuccessBlock:^id(BFTask *task) {
|
||||
PFObject *valedictorian = task.result;
|
||||
return [self findAsync:query];
|
||||
}] continueWithSuccessBlock:^id(BFTask *task) {
|
||||
NSArray *students = task.result;
|
||||
PFObject *salutatorian = [students objectAtIndex:1];
|
||||
[salutatorian setObject:@YES forKey:@"salutatorian"];
|
||||
return [self saveAsync:salutatorian];
|
||||
}] continueWithSuccessBlock:^id(BFTask *task) {
|
||||
// Everything is done!
|
||||
return nil;
|
||||
}];
|
||||
```
|
||||
|
||||
```swift
|
||||
// Swift
|
||||
var query = PFQuery(className:"Student")
|
||||
query.orderByDescending("gpa")
|
||||
findAsync(query).continueWithSuccessBlock {
|
||||
(task: BFTask!) -> BFTask in
|
||||
let students = task.result() as NSArray
|
||||
var valedictorian = students.objectAtIndex(0) as PFObject
|
||||
valedictorian["valedictorian"] = true
|
||||
return self.saveAsync(valedictorian)
|
||||
}.continueWithSuccessBlock {
|
||||
(task: BFTask!) -> BFTask in
|
||||
var valedictorian = task.result() as PFObject
|
||||
return self.findAsync(query)
|
||||
}.continueWithSuccessBlock {
|
||||
(task: BFTask!) -> BFTask in
|
||||
let students = task.result() as NSArray
|
||||
var salutatorian = students.objectAtIndex(1) as PFObject
|
||||
salutatorian["salutatorian"] = true
|
||||
return self.saveAsync(salutatorian)
|
||||
}.continueWithSuccessBlock {
|
||||
(task: BFTask!) -> AnyObject! in
|
||||
// Everything is done!
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
By carefully choosing whether to call `continueWithBlock:` or `continueWithSuccessBlock:`, you can control how errors are propagated in your application. Using `continueWithBlock:` lets you handle errors by transforming them or dealing with them. You can think of failed tasks kind of like throwing an exception. In fact, if you throw an exception inside a continuation, the resulting task will be faulted with that exception.
|
||||
|
||||
```objective-c
|
||||
// Objective-C
|
||||
PFQuery *query = [PFQuery queryWithClassName:@"Student"];
|
||||
[query orderByDescending:@"gpa"];
|
||||
[[[[[self findAsync:query] continueWithSuccessBlock:^id(BFTask *task) {
|
||||
NSArray *students = task.result;
|
||||
PFObject *valedictorian = [students objectAtIndex:0];
|
||||
[valedictorian setObject:@YES forKey:@"valedictorian"];
|
||||
// Force this callback to fail.
|
||||
return [BFTask taskWithError:[NSError errorWithDomain:@"example.com"
|
||||
code:-1
|
||||
userInfo:nil]];
|
||||
}] continueWithSuccessBlock:^id(BFTask *task) {
|
||||
// Now this continuation will be skipped.
|
||||
PFQuery *valedictorian = task.result;
|
||||
return [self findAsync:query];
|
||||
}] continueWithBlock:^id(BFTask *task) {
|
||||
if (task.error) {
|
||||
// This error handler WILL be called.
|
||||
// The error will be the NSError returned above.
|
||||
// Let's handle the error by returning a new value.
|
||||
// The task will be completed with nil as its value.
|
||||
return nil;
|
||||
}
|
||||
// This will also be skipped.
|
||||
NSArray *students = task.result;
|
||||
PFObject *salutatorian = [students objectAtIndex:1];
|
||||
[salutatorian setObject:@YES forKey:@"salutatorian"];
|
||||
return [self saveAsync:salutatorian];
|
||||
}] continueWithSuccessBlock:^id(BFTask *task) {
|
||||
// Everything is done! This gets called.
|
||||
// The task's result is nil.
|
||||
return nil;
|
||||
}];
|
||||
```
|
||||
|
||||
```swift
|
||||
// Swift
|
||||
var query = PFQuery(className:"Student")
|
||||
query.orderByDescending("gpa")
|
||||
findAsync(query).continueWithSuccessBlock {
|
||||
(task: BFTask!) -> BFTask in
|
||||
let students = task.result() as NSArray
|
||||
var valedictorian = students.objectAtIndex(0) as PFObject
|
||||
valedictorian["valedictorian"] = true
|
||||
//Force this callback to fail.
|
||||
return BFTask(error:NSError(domain:"example.com",
|
||||
code:-1, userInfo: nil))
|
||||
}.continueWithSuccessBlock {
|
||||
(task: BFTask!) -> AnyObject! in
|
||||
//Now this continuation will be skipped.
|
||||
var valedictorian = task.result() as PFObject
|
||||
return self.findAsync(query)
|
||||
}.continueWithBlock {
|
||||
(task: BFTask!) -> AnyObject! in
|
||||
if task.error() {
|
||||
// This error handler WILL be called.
|
||||
// The error will be the NSError returned above.
|
||||
// Let's handle the error by returning a new value.
|
||||
// The task will be completed with nil as its value.
|
||||
return nil
|
||||
}
|
||||
// This will also be skipped.
|
||||
let students = task.result() as NSArray
|
||||
var salutatorian = students.objectAtIndex(1) as PFObject
|
||||
salutatorian["salutatorian"] = true
|
||||
return self.saveAsync(salutatorian)
|
||||
}.continueWithSuccessBlock {
|
||||
(task: BFTask!) -> AnyObject! in
|
||||
// Everything is done! This gets called.
|
||||
// The tasks result is nil.
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
It's often convenient to have a long chain of success callbacks with only one error handler at the end.
|
||||
|
||||
## Creating Tasks
|
||||
|
||||
When you're getting started, you can just use the tasks returned from methods like `findAsync:` or `saveAsync:`. However, for more advanced scenarios, you may want to make your own tasks. To do that, you create a `BFTaskCompletionSource`. This object will let you create a new `BFTask`, and control whether it gets marked as finished or cancelled. After you create a `BFTaskCompletionSource`, you'll need to call `setResult:`, `setError:`, or `cancel` to trigger its continuations.
|
||||
|
||||
```objective-c
|
||||
// Objective-C
|
||||
- (BFTask *)successAsync {
|
||||
BFTaskCompletionSource *successful = [BFTaskCompletionSource taskCompletionSource];
|
||||
[successful setResult:@"The good result."];
|
||||
return successful.task;
|
||||
}
|
||||
|
||||
- (BFTask *)failAsync {
|
||||
BFTaskCompletionSource *failed = [BFTaskCompletionSource taskCompletionSource];
|
||||
[failed setError:[NSError errorWithDomain:@"example.com" code:-1 userInfo:nil]];
|
||||
return failed.task;
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
// Swift
|
||||
func successAsync() -> BFTask {
|
||||
var successful = BFTaskCompletionSource()
|
||||
successful.setResult("The good result.")
|
||||
return successful.task
|
||||
}
|
||||
|
||||
func failAsync() -> BFTask {
|
||||
var failed = BFTaskCompletionSource()
|
||||
failed.setError(NSError(domain:"example.com", code:-1, userInfo:nil))
|
||||
return failed.task
|
||||
}
|
||||
```
|
||||
|
||||
If you know the result of a task at the time it is created, there are some convenience methods you can use.
|
||||
|
||||
```objective-c
|
||||
// Objective-C
|
||||
BFTask *successful = [BFTask taskWithResult:@"The good result."];
|
||||
|
||||
BFTask *failed = [BFTask taskWithError:anError];
|
||||
```
|
||||
|
||||
```swift
|
||||
// Swift
|
||||
let successful = BFTask(result:"The good result")
|
||||
|
||||
let failed = BFTask(error:anError)
|
||||
```
|
||||
|
||||
## Creating Async Methods
|
||||
|
||||
With these tools, it's easy to make your own asynchronous functions that return tasks. For example, you can make a task-based version of `fetchAsync:` easily.
|
||||
|
||||
```objective-c
|
||||
// Objective-C
|
||||
- (BFTask *) fetchAsync:(PFObject *)object {
|
||||
BFTaskCompletionSource *task = [BFTaskCompletionSource taskCompletionSource];
|
||||
[object fetchInBackgroundWithBlock:^(PFObject *object, NSError *error) {
|
||||
if (!error) {
|
||||
[task setResult:object];
|
||||
} else {
|
||||
[task setError:error];
|
||||
}
|
||||
}];
|
||||
return task.task;
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
// Swift
|
||||
func fetchAsync(object: PFObject) -> BFTask {
|
||||
var task = BFTaskCompletionSource()
|
||||
object.fetchInBackgroundWithBlock {
|
||||
(object: PFObject?, error: NSError?) -> Void in
|
||||
if error == nil {
|
||||
task.setResult(object)
|
||||
} else {
|
||||
task.setError(error)
|
||||
}
|
||||
}
|
||||
return task.task
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
It's similarly easy to create `saveAsync:`, `findAsync:` or `deleteAsync:`.
|
||||
|
||||
## Tasks in Series
|
||||
|
||||
`BFTasks` are convenient when you want to do a series of tasks in a row, each one waiting for the previous to finish. For example, imagine you want to delete all of the comments on your blog.
|
||||
|
||||
```objective-c
|
||||
// Objective-C
|
||||
PFQuery *query = [PFQuery queryWithClassName:@"Comments"];
|
||||
[query whereKey:@"post" equalTo:@123];
|
||||
|
||||
[[[self findAsync:query] continueWithBlock:^id(BFTask *task) {
|
||||
NSArray *results = task.result;
|
||||
|
||||
// Create a trivial completed task as a base case.
|
||||
BFTask *task = [BFTask taskWithResult:nil];
|
||||
for (PFObject *result in results) {
|
||||
// For each item, extend the task with a function to delete the item.
|
||||
task = [task continueWithBlock:^id(BFTask *task) {
|
||||
// Return a task that will be marked as completed when the delete is finished.
|
||||
return [self deleteAsync:result];
|
||||
}];
|
||||
}
|
||||
return task;
|
||||
}] continueWithBlock:^id(BFTask *task) {
|
||||
// Every comment was deleted.
|
||||
return nil;
|
||||
}];
|
||||
```
|
||||
|
||||
```swift
|
||||
// Swift
|
||||
var query = PFQuery(className:"Comments")
|
||||
query.whereKey("post", equalTo:123)
|
||||
findAsync(query).continueWithBlock {
|
||||
(task: BFTask!) -> BFTask in
|
||||
let results = task.result() as NSArray
|
||||
|
||||
// Create a trivial completed task as a base case.
|
||||
let task = BFTask(result:nil)
|
||||
for result : PFObject in results {
|
||||
// For each item, extend the task with a function to delete the item.
|
||||
task = task.continueWithBlock {
|
||||
(task: BFTask!) -> BFTask in
|
||||
return self.deleteAsync(result)
|
||||
}
|
||||
}
|
||||
return task
|
||||
}.continueWithBlock {
|
||||
(task: BFTask!) -> AnyObject! in
|
||||
// Every comment was deleted.
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## Tasks in Parallel
|
||||
|
||||
You can also perform several tasks in parallel, using the `taskForCompletionOfAllTasks:` method. You can start multiple operations at once, and use `taskForCompletionOfAllTasks:` to create a new task that will be marked as completed when all of its input tasks are completed. The new task will be successful only if all of the passed-in tasks succeed. Performing operations in parallel will be faster than doing them serially, but may consume more system resources and bandwidth.
|
||||
|
||||
```objective-c
|
||||
// Objective-C
|
||||
PFQuery *query = [PFQuery queryWithClassName:@"Comments"];
|
||||
[query whereKey:@"post" equalTo:@123];
|
||||
|
||||
[[[self findAsync:query] continueWithBlock:^id(BFTask *results) {
|
||||
// Collect one task for each delete into an array.
|
||||
NSMutableArray *tasks = [NSMutableArray array];
|
||||
for (PFObject *result in results) {
|
||||
// Start this delete immediately and add its task to the list.
|
||||
[tasks addObject:[self deleteAsync:result]];
|
||||
}
|
||||
// Return a new task that will be marked as completed when all of the deletes are
|
||||
// finished.
|
||||
return [BFTask taskForCompletionOfAllTasks:tasks];
|
||||
}] continueWithBlock:^id(BFTask *task) {
|
||||
// Every comment was deleted.
|
||||
return nil;
|
||||
}];
|
||||
```
|
||||
|
||||
```swift
|
||||
// Swift
|
||||
var query = PFQuery(className:"Comments")
|
||||
query.whereKey("post", equalTo:123)
|
||||
|
||||
findAsync(query).continueWithBlock {
|
||||
(task: BFTask!) -> BFTask in
|
||||
// Collect one task for each delete into an array.
|
||||
var tasks = NSMutableArray.array()
|
||||
var results = task.result() as NSArray
|
||||
for result : PFObject! in results {
|
||||
// Start this delete immediately and add its task to the list.
|
||||
tasks.addObject(self.deleteAsync(result))
|
||||
}
|
||||
// Return a new task that will be marked as completed when all of the deletes
|
||||
// are finished.
|
||||
return BFTask(forCompletionOfAllTasks:tasks)
|
||||
}.continueWithBlock {
|
||||
(task: BFTask!) -> AnyObject! in
|
||||
// Every comment was deleted.
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## Task Executors
|
||||
|
||||
Both `continueWithBlock:` and `continueWithSuccessBlock:` methods have another form that takes an instance of `BFExecutor`. These are `continueWithExecutor:withBlock:` and `continueWithExecutor:withSuccessBlock:`. These methods allow you to control how the continuation is executed. The default executor will dispatch to GCD, but you can provide your own executor to schedule work onto a different thread. For example, if you want to continue with work on the UI thread:
|
||||
|
||||
```objective-c
|
||||
// Create a BFExecutor that uses the main thread.
|
||||
BFExecutor *myExecutor = [BFExecutor executorWithBlock:^void(void(^block)()) {
|
||||
dispatch_async(dispatch_get_main_queue(), block);
|
||||
}];
|
||||
|
||||
// And use the Main Thread Executor like this. The executor applies only to the new
|
||||
// continuation being passed into continueWithBlock.
|
||||
[[self fetchAsync:object] continueWithExecutor:myExecutor withBlock:^id(BFTask *task) {
|
||||
myTextView.text = [object objectForKey:@"name"];
|
||||
}];
|
||||
```
|
||||
|
||||
For common cases, such as dispatching on the main thread, we have provided default implementations of `BFExecutor`. These include `defaultExecutor`, `immediateExecutor`, `mainThreadExecutor`, `executorWithDispatchQueue:`, and `executorWithOperationQueue:`. For example:
|
||||
|
||||
```objective-c
|
||||
// Continue on the Main Thread, using a built-in executor.
|
||||
[[self fetchAsync:object] continueWithExecutor:[BFExecutor mainThreadExecutor] withBlock:^id(BFTask *task) {
|
||||
myTextView.text = [object objectForKey:@"name"];
|
||||
}];
|
||||
```
|
||||
|
||||
## Task Cancellation
|
||||
|
||||
It's generally bad design to keep track of the `BFTaskCompletionSource` for cancellation. A better model is to create a "cancellation token" at the top level, and pass that to each async function that you want to be part of the same "cancelable operation". Then, in your continuation blocks, you can check whether the cancellation token has been cancelled and bail out early by returning a `[BFTask cancelledTask]`. For example:
|
||||
|
||||
```objective-c
|
||||
- (void)doSomethingComplicatedAsync:(MYCancellationToken *)cancellationToken {
|
||||
[[self doSomethingAsync:cancellationToken] continueWithBlock:^{
|
||||
if (cancellationToken.isCancelled) {
|
||||
return [BFTask cancelledTask];
|
||||
}
|
||||
// Do something that takes a while.
|
||||
return result;
|
||||
}];
|
||||
}
|
||||
|
||||
// Somewhere else.
|
||||
MYCancellationToken *cancellationToken = [[MYCancellationToken alloc] init];
|
||||
[obj doSomethingComplicatedAsync:cancellationToken];
|
||||
|
||||
// When you get bored...
|
||||
[cancellationToken cancel];
|
||||
```
|
||||
|
||||
**Note:** The cancellation token implementation should be thread-safe.
|
||||
We are likely to add some concept like this to Bolts at some point in the future.
|
||||
|
||||
# App Links
|
||||
|
||||
[App Links](http://www.applinks.org) provide a cross-platform mechanism that allows a developer to define and publish a deep-linking scheme for their content, allowing other apps to link directly to an experience optimized for the device they are running on. Whether you are building an app that receives incoming links or one that may link out to other apps' content, Bolts provides tools to simplify implementation of the [App Links protocol](http://www.applinks.org/documentation).
|
||||
|
||||
## Handling an App Link
|
||||
|
||||
The most common case will be making your app receive App Links. In-linking will allow your users to quickly access the richest, most native-feeling presentation of linked content on their devices. Bolts makes it easy to handle an inbound App Link (as well as general inbound deep-links) by providing utilities for processing an incoming URL.
|
||||
|
||||
For example, you can use the `BFURL` utility class to parse an incoming URL in your `AppDelegate`:
|
||||
|
||||
```objective-c
|
||||
- (BOOL)application:(UIApplication *)application
|
||||
openURL:(NSURL *)url
|
||||
sourceApplication:(NSString *)sourceApplication
|
||||
annotation:(id)annotation {
|
||||
BFURL *parsedUrl = [BFURL URLWithInboundURL:url sourceApplication:sourceApplication];
|
||||
|
||||
// Use the target URL from the App Link to locate content.
|
||||
if ([parsedUrl.targetURL.pathComponents[1] isEqualToString:@"profiles"]) {
|
||||
// Open a profile viewer.
|
||||
}
|
||||
|
||||
// You can also check the query string easily.
|
||||
NSString *query = parsedUrl.targetQueryParameters[@"query"];
|
||||
|
||||
// Apps that have existing deep-linking support and map their App Links to existing
|
||||
// deep-linking functionality may instead want to perform these operations on the input URL.
|
||||
// Use the target URL from the App Link to locate content.
|
||||
if ([parsedUrl.inputURL.pathComponents[1] isEqualToString:@"profiles"]) {
|
||||
// Open a profile viewer.
|
||||
}
|
||||
|
||||
// You can also check the query string easily.
|
||||
NSString *query = parsedUrl.inputQueryParameters[@"query"];
|
||||
|
||||
// Apps can easily check the Extras and App Link data from the App Link as well.
|
||||
NSString *fbAccessToken = parsedUrl.appLinkExtras[@"fb_access_token"];
|
||||
NSDictionary *refererData = parsedUrl.appLinkExtras[@"referer"];
|
||||
}
|
||||
```
|
||||
|
||||
## Navigating to a URL
|
||||
|
||||
Following an App Link allows your app to provide the best user experience (as defined by the receiving app) when a user navigates to a link. Bolts makes this process simple, automating the steps required to follow a link:
|
||||
|
||||
1. Resolve the App Link by getting the App Link metadata from the HTML at the URL specified.
|
||||
2. Step through App Link targets relevant to the device being used, checking whether the app that can handle the target is present on the device.
|
||||
3. If an app is present, build a URL with the appropriate al_applink_data specified and navigate to that URL.
|
||||
4. Otherwise, open the browser with the original URL specified.
|
||||
|
||||
In the simplest case, it takes just one line of code to navigate to a URL that may have an App Link:
|
||||
|
||||
```objective-c
|
||||
[BFAppLinkNavigation navigateToURLInBackground:url];
|
||||
```
|
||||
|
||||
### Adding App and Navigation Data
|
||||
|
||||
Under most circumstances, the data that will need to be passed along to an app during a navigation will be contained in the URL itself, so that whether or not the app is actually installed on the device, users are taken to the correct content. Occasionally, however, apps will want to pass along data that is relevant for app-to-app navigation, or will want to augment the App Link protocol with information that might be used by the app to adjust how the app should behave (e.g. showing a link back to the referring app).
|
||||
|
||||
If you want to take advantage of these features, you can break apart the navigation process. First, you must have an App Link to which you wish to navigate:
|
||||
|
||||
```objective-c
|
||||
[[BFAppLinkNavigation resolveAppLinkInBackground:url] continueWithSuccessBlock:^id(BFTask *task) {
|
||||
BFAppLink *link = task.result;
|
||||
}];
|
||||
```
|
||||
|
||||
Then, you can build an App Link request with any additional data you would like and navigate:
|
||||
|
||||
```objective-c
|
||||
BFAppLinkNavigation *navigation = [BFAppLinkNavigation navigationWithAppLink:link
|
||||
extras:@{ @"access_token": @"t0kEn" }
|
||||
appLinkData:@{ @"ref": @"12345" }];
|
||||
NSError *error = nil;
|
||||
[navigation navigate:&error];
|
||||
```
|
||||
|
||||
### Resolving App Link Metadata
|
||||
|
||||
Bolts allows for custom App Link resolution, which may be used as a performance optimization (e.g. caching the metadata) or as a mechanism to allow developers to use a centralized index for obtaining App Link metadata. A custom App Link resolver just needs to be able to take a URL and return a `BFAppLink` containing the ordered list of `BFAppLinkTarget`s that are applicable for this device. Bolts provides one of these out of the box that performs this resolution on the device using a hidden UIWebView.
|
||||
|
||||
You can use any resolver that implements the `BFAppLinkResolving` protocol by using one of the overloads on `BFAppLinkNavigation`:
|
||||
|
||||
```objective-c
|
||||
[BFAppLinkNavigation navigateToURLInBackground:url
|
||||
resolver:resolver];
|
||||
```
|
||||
|
||||
Alternatively, a you can swap out the default resolver to be used by the built-in APIs:
|
||||
|
||||
```objective-c
|
||||
[BFAppLinkNavigation setDefaultResolver:resolver];
|
||||
[BFAppLinkNavigation navigateToURLInBackground:url];
|
||||
```
|
||||
|
||||
## App Link Return-to-Referer View
|
||||
|
||||
When an application is opened via an App Link, a banner allowing the user to "Touch to return to <calling app>" should be displayed. The `BFAppLinkReturnToRefererView` provides this functionality. It will take an incoming App Link and parse the referer information to display the appropriate calling app name.
|
||||
|
||||
```objective-c
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
// Perform other view initialization.
|
||||
|
||||
self.returnToRefererController = [[BFAppLinkReturnToRefererController alloc] init];
|
||||
|
||||
// self.returnToRefererView is a BFAppLinkReturnToRefererView.
|
||||
// You may initialize the view either by loading it from a NIB or programmatically.
|
||||
self.returnToRefererController.view = self.returnToRefererView;
|
||||
|
||||
// If you have a UINavigationController in the view, then the bar must be shown above it.
|
||||
[self.returnToRefererController]
|
||||
}
|
||||
```
|
||||
|
||||
The following code assumes that the view controller has an `openedAppLinkURL` `NSURL` property that has already been populated with the URL used to open the app. You can then do something like this to show the view:
|
||||
|
||||
```objective-c
|
||||
- (void)viewWillAppear {
|
||||
[super viewWillAppear];
|
||||
|
||||
// Show only if you have a back AppLink.
|
||||
[self.returnToRefererController showViewForRefererURL:self.openedAppLinkURL];
|
||||
}
|
||||
```
|
||||
|
||||
In a navigaton-controller view hierarchy, the banner should be displayed above the navigation bar, and `BFAppLinkReturnToRefererController` provides an `initForDisplayAboveNavController` method to assist with this.
|
||||
|
||||
## Analytics
|
||||
|
||||
Bolts introduces Measurement Event. App Links posts three different Measurement Event notifications to the application, which can be caught and integrated with existing analytics components in your application.
|
||||
|
||||
* `al_nav_out` — Raised when your app switches out to an App Links URL.
|
||||
* `al_nav_in` — Raised when your app opens an incoming App Links URL.
|
||||
* `al_ref_back_out` — Raised when your app returns back the referrer app using the built-in top navigation back bar view.
|
||||
|
||||
### Listen for App Links Measurement Events
|
||||
|
||||
There are other analytics tools that are integrated with Bolts' App Links events, but you can also listen for these events yourself:
|
||||
|
||||
```objective-c
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:BFMeasurementEventNotificationName object:nil queue:nil usingBlock:^(NSNotification *note) {
|
||||
NSDictionary *event = note.userInfo;
|
||||
NSDictionary *eventData = event[BFMeasurementEventArgsKey];
|
||||
// Integrate to your logging/analytics component.
|
||||
}];
|
||||
```
|
||||
|
||||
### App Links Event Fields
|
||||
|
||||
App Links Measurement Events sends additional information from App Links Intents in flattened string key value pairs. Here are some of the useful fields for the three events.
|
||||
|
||||
* `al_nav_in`
|
||||
* `inputURL`: the URL that opens the app.
|
||||
* `inputURLScheme`: the scheme of `inputURL`.
|
||||
* `refererURL`: the URL that the referrer app added into `al_applink_data`: `referer_app_link`.
|
||||
* `refererAppName`: the app name that the referrer app added to `al_applink_data`: `referer_app_link`.
|
||||
* `sourceApplication`: the bundle of referrer application.
|
||||
* `targetURL`: the `target_url` field in `al_applink_data`.
|
||||
* `version`: App Links API version.
|
||||
|
||||
* `al_nav_out` / `al_ref_back_out`
|
||||
* `outputURL`: the URL used to open the other app (or browser). If there is an eligible app to open, this will be the custom scheme url/intent in `al_applink_data`.
|
||||
* `outputURLScheme`: the scheme of `outputURL`.
|
||||
* `sourceURL`: the URL of the page hosting App Links meta tags.
|
||||
* `sourceURLHost`: the hostname of `sourceURL`.
|
||||
* `success`: `“1”` to indicate success in opening the App Link in another app or browser; `“0”` to indicate failure to open the App Link.
|
||||
* `type`: `“app”` for open in app, `“web”` for open in browser; `“fail”` when the success field is `“0”`.
|
||||
* `version`: App Links API version.
|
||||
|
||||
# Installation
|
||||
|
||||
You can download the latest framework files from our [Releases page](https://github.com/BoltsFramework/Bolts-iOS/releases).
|
||||
|
||||
Bolts is also available through [CocoaPods](http://cocoapods.org). To install it simply add the following line to your Podfile:
|
||||
|
||||
pod 'Bolts'
|
||||
Reference in new issue
Block a user