- 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
|
||||
Reference in new issue
Block a user