├── .clang-format ├── .codecov.yml ├── .gitignore ├── .gitmodules ├── .travis.yml ├── Bolts.podspec ├── Bolts.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── Bolts-OSX.xcscheme~Unify schemes and targets names. │ ├── Bolts-OSX.xcscheme~Unify schemes and targets names._0 │ ├── Bolts-OSX.xcscheme~beac94366f4186a4a54100ce87c7222b6008d63b │ ├── Bolts-OSX.xcscheme~beac94366f4186a4a54100ce87c7222b6008d63b_0 │ ├── Bolts-iOS-Dynamic.xcscheme │ ├── Bolts-iOS.xcscheme │ ├── Bolts-macOS.xcscheme │ ├── Bolts-tvOS-Dynamic.xcscheme │ ├── Bolts-tvOS.xcscheme │ ├── Bolts-watchOS-Dynamic.xcscheme │ └── Bolts-watchOS.xcscheme ├── Bolts ├── Common │ ├── BFCancellationToken.h │ ├── BFCancellationToken.m │ ├── BFCancellationTokenRegistration.h │ ├── BFCancellationTokenRegistration.m │ ├── BFCancellationTokenSource.h │ ├── BFCancellationTokenSource.m │ ├── BFExecutor.h │ ├── BFExecutor.m │ ├── BFGeneric.h │ ├── BFTask.h │ ├── BFTask.m │ ├── BFTaskCompletionSource.h │ ├── BFTaskCompletionSource.m │ ├── Bolts.h │ └── Bolts.m ├── Resources │ ├── Info.plist │ └── iOS.modulemap ├── en.lproj │ └── Localizable.strings └── iOS │ ├── BFAppLink.h │ ├── BFAppLink.m │ ├── BFAppLinkNavigation.h │ ├── BFAppLinkNavigation.m │ ├── BFAppLinkResolving.h │ ├── BFAppLinkReturnToRefererController.h │ ├── BFAppLinkReturnToRefererController.m │ ├── BFAppLinkReturnToRefererView.h │ ├── BFAppLinkReturnToRefererView.m │ ├── BFAppLinkTarget.h │ ├── BFAppLinkTarget.m │ ├── BFMeasurementEvent.h │ ├── BFMeasurementEvent.m │ ├── BFURL.h │ ├── BFURL.m │ ├── BFWebViewAppLinkResolver.h │ ├── BFWebViewAppLinkResolver.m │ └── Internal │ ├── BFAppLinkReturnToRefererView_Internal.h │ ├── BFAppLink_Internal.h │ ├── BFMeasurementEvent_Internal.h │ └── BFURL_Internal.h ├── BoltsTestUI ├── AppDelegate.h ├── AppDelegate.m ├── BoltsTestUI-Info.plist ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json ├── en.lproj │ └── InfoPlist.strings ├── main.m └── test.html ├── BoltsTests ├── AppLinkReturnToRefererViewTests.m ├── AppLinkTests.m ├── BoltsTests-Info.plist ├── CancellationTests.m ├── ExecutorTests.m ├── TaskTests.m └── en.lproj │ └── InfoPlist.strings ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Configurations ├── Bolts-iOS-Dynamic.xcconfig ├── Bolts-iOS.xcconfig ├── Bolts-macOS.xcconfig ├── Bolts-tvOS-Dynamic.xcconfig ├── Bolts-tvOS.xcconfig ├── Bolts-watchOS-Dynamic.xcconfig ├── Bolts-watchOS.xcconfig ├── BoltsTests-OSX.xcconfig ├── BoltsTests-iOS.xcconfig ├── BoltsTests-tvOS.xcconfig ├── Shared └── Version.xcconfig ├── LICENSE ├── PATENTS ├── README.md └── scripts ├── build_all.sh ├── build_framework.sh ├── build_release.sh └── common.sh /.clang-format: -------------------------------------------------------------------------------- 1 | Vendor/xctoolchain/.clang-format -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | ignore: 3 | - BoltsTests/.* 4 | - BoltsTestUI/.* 5 | status: 6 | patch: false 7 | changes: false 8 | project: 9 | default: 10 | target: 75 11 | comment: false 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## OS X 2 | .DS_Store 3 | 4 | ## Build generated 5 | build/ 6 | DerivedData 7 | 8 | ## Various settings 9 | *.pbxuser 10 | !default.pbxuser 11 | *.mode1v3 12 | !default.mode1v3 13 | *.mode2v3 14 | !default.mode2v3 15 | *.perspectivev3 16 | !default.perspectivev3 17 | xcuserdata 18 | 19 | ## Other 20 | *.xccheckout 21 | *.moved-aside 22 | *.xcuserstate 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | 29 | ## Dependency Managers 30 | Pods/ 31 | Carthage/Build 32 | 33 | ## AppCode 34 | .idea/ 35 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Vendor/xctoolchain"] 2 | path = Vendor/xctoolchain 3 | url = https://github.com/nlutsenko/xctoolchain.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | branches: 2 | only: 3 | - master 4 | language: objective-c 5 | os: osx 6 | osx_image: xcode10 7 | cache: 8 | - cocoapods 9 | env: 10 | matrix: 11 | - TEST_TYPE=iOS 12 | - TEST_TYPE=macOS 13 | - TEST_TYPE=tvOS 14 | - TEST_TYPE=CocoaPods 15 | - TEST_TYPE=Carthage 16 | before_install: 17 | - | 18 | if [ "$TEST_TYPE" = iOS ] || [ "$TEST_TYPE" = macOS ] || [ "$TEST_TYPE" = tvOS ]; then 19 | gem install xcpretty -N --no-document 20 | elif [ "$TEST_TYPE" = CocoaPods ]; then 21 | gem install cocoapods --pre --no-document 22 | elif [ "$TEST_TYPE" = Carthage ]; then 23 | brew update 24 | brew install carthage || brew upgrade carthage 25 | fi 26 | script: 27 | - | 28 | case $TEST_TYPE in 29 | iOS) 30 | set -o pipefail 31 | xcodebuild test -project Bolts.xcodeproj -sdk iphonesimulator -scheme Bolts-iOS -configuration Debug -destination "platform=iOS Simulator,name=iPhone SE" GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES GCC_GENERATE_TEST_COVERAGE_FILES=YES | xcpretty -c 32 | ;; 33 | macOS) 34 | set -o pipefail 35 | xcodebuild test -project Bolts.xcodeproj -sdk macosx -scheme Bolts-macOS -configuration Debug GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES GCC_GENERATE_TEST_COVERAGE_FILES=YES | xcpretty -c 36 | ;; 37 | tvOS) 38 | set -o pipefail 39 | xcodebuild test -project Bolts.xcodeproj -sdk appletvsimulator -scheme Bolts-tvOS -destination "platform=tvOS Simulator,name=Apple TV" -configuration Debug GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES GCC_GENERATE_TEST_COVERAGE_FILES=YES | xcpretty -c 40 | ;; 41 | CocoaPods) 42 | pod lib lint --use-libraries Bolts.podspec 43 | pod lib lint Bolts.podspec 44 | ;; 45 | Carthage) 46 | carthage build --no-skip-current 47 | ;; 48 | *) 49 | ;; 50 | esac 51 | after_success: 52 | - | 53 | if [ "$TEST_TYPE" = iOS ] || [ "$TEST_TYPE" = OSX ] || [ "$TEST_TYPE" = tvOS ]; then 54 | bash <(curl -s https://codecov.io/bash) 55 | fi 56 | -------------------------------------------------------------------------------- /Bolts.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'Bolts' 3 | s.version = '1.9.1' 4 | s.summary = 'Bolts is a collection of low-level libraries designed to make developing mobile apps easier.' 5 | s.description = <<-DESC 6 | Bolts was designed by Parse and Facebook for our own internal use, and we have decided to open source these libraries to make them available to others. Using these libraries does not require using any Parse services. Nor do they require having a Parse or Facebook developer account. 7 | 8 | The first component in Bolts is "tasks", which make organization of complex asynchronous code more manageable. A task is kind of like a JavaScript Promise, but available for iOS and Android. 9 | DESC 10 | s.homepage = 'https://github.com/BoltsFramework' 11 | s.authors = { 'Nikita Lutsenko' => 'nlutsenko@me.com' } 12 | s.license = 'BSD' 13 | s.source = { :git => 'https://github.com/BoltsFramework/Bolts-ObjC.git', :tag => s.version.to_s } 14 | s.social_media_url = 'https://twitter.com/ParseIt' 15 | s.requires_arc = true 16 | 17 | s.ios.deployment_target = '8.0' 18 | s.osx.deployment_target = '10.8' 19 | s.watchos.deployment_target = '2.0' 20 | s.tvos.deployment_target = '9.0' 21 | 22 | s.subspec 'Tasks' do |ss| 23 | ss.source_files = 'Bolts/Common/*.[hm]' 24 | ss.public_header_files = 'Bolts/Common/*.h' 25 | end 26 | 27 | s.subspec 'AppLinks' do |ss| 28 | ss.ios.deployment_target = '8.0' 29 | ss.dependency 'Bolts/Tasks' 30 | 31 | ss.ios.source_files = 'Bolts/iOS/**/*.[hm]' 32 | ss.ios.public_header_files = 'Bolts/iOS/*.h' 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /Bolts.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Bolts.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Bolts.xcodeproj/xcshareddata/xcschemes/Bolts-OSX.xcscheme~Unify schemes and targets names.: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 55 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 74 | 76 | 77 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /Bolts.xcodeproj/xcshareddata/xcschemes/Bolts-OSX.xcscheme~Unify schemes and targets names._0: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 55 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 74 | 76 | 77 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /Bolts.xcodeproj/xcshareddata/xcschemes/Bolts-OSX.xcscheme~beac94366f4186a4a54100ce87c7222b6008d63b: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 16 | 22 | 23 | 24 | 31 | 37 | 38 | 39 | 40 | 41 | 47 | 48 | 50 | 56 | 57 | 58 | 59 | 60 | 66 | 67 | 68 | 69 | 70 | 71 | 81 | 82 | 88 | 89 | 90 | 91 | 92 | 93 | 99 | 100 | 106 | 107 | 108 | 109 | 111 | 112 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /Bolts.xcodeproj/xcshareddata/xcschemes/Bolts-OSX.xcscheme~beac94366f4186a4a54100ce87c7222b6008d63b_0: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 16 | 22 | 23 | 24 | 31 | 37 | 38 | 39 | 40 | 41 | 47 | 48 | 50 | 56 | 57 | 58 | 59 | 60 | 66 | 67 | 68 | 69 | 70 | 71 | 81 | 82 | 88 | 89 | 90 | 91 | 92 | 93 | 99 | 100 | 106 | 107 | 108 | 109 | 111 | 112 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /Bolts.xcodeproj/xcshareddata/xcschemes/Bolts-iOS-Dynamic.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /Bolts.xcodeproj/xcshareddata/xcschemes/Bolts-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 45 | 46 | 52 | 53 | 54 | 55 | 57 | 63 | 64 | 65 | 66 | 67 | 77 | 78 | 84 | 85 | 86 | 87 | 93 | 94 | 100 | 101 | 102 | 103 | 105 | 106 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /Bolts.xcodeproj/xcshareddata/xcschemes/Bolts-macOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 42 | 48 | 49 | 50 | 51 | 52 | 62 | 63 | 69 | 70 | 71 | 72 | 78 | 79 | 85 | 86 | 87 | 88 | 90 | 91 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /Bolts.xcodeproj/xcshareddata/xcschemes/Bolts-tvOS-Dynamic.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /Bolts.xcodeproj/xcshareddata/xcschemes/Bolts-tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 45 | 46 | 52 | 53 | 54 | 55 | 57 | 63 | 64 | 65 | 66 | 67 | 77 | 78 | 84 | 85 | 86 | 87 | 93 | 94 | 100 | 101 | 102 | 103 | 105 | 106 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /Bolts.xcodeproj/xcshareddata/xcschemes/Bolts-watchOS-Dynamic.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /Bolts.xcodeproj/xcshareddata/xcschemes/Bolts-watchOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 38 | 39 | 40 | 41 | 42 | 43 | 53 | 54 | 60 | 61 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /Bolts/Common/BFCancellationToken.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | 13 | #import 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | /*! 18 | A block that will be called when a token is cancelled. 19 | */ 20 | typedef void(^BFCancellationBlock)(void); 21 | 22 | /*! 23 | The consumer view of a CancellationToken. 24 | Propagates notification that operations should be canceled. 25 | A BFCancellationToken has methods to inspect whether the token has been cancelled. 26 | */ 27 | @interface BFCancellationToken : NSObject 28 | 29 | /*! 30 | Whether cancellation has been requested for this token source. 31 | */ 32 | @property (nonatomic, assign, readonly, getter=isCancellationRequested) BOOL cancellationRequested; 33 | 34 | /*! 35 | Register a block to be notified when the token is cancelled. 36 | If the token is already cancelled the delegate will be notified immediately. 37 | */ 38 | - (BFCancellationTokenRegistration *)registerCancellationObserverWithBlock:(BFCancellationBlock)block; 39 | 40 | @end 41 | 42 | NS_ASSUME_NONNULL_END 43 | -------------------------------------------------------------------------------- /Bolts/Common/BFCancellationToken.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import "BFCancellationToken.h" 12 | #import "BFCancellationTokenRegistration.h" 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | @interface BFCancellationToken () 17 | 18 | @property (nullable, nonatomic, strong) NSMutableArray *registrations; 19 | @property (nonatomic, strong) NSObject *lock; 20 | @property (nonatomic) BOOL disposed; 21 | 22 | @end 23 | 24 | @interface BFCancellationTokenRegistration (BFCancellationToken) 25 | 26 | + (instancetype)registrationWithToken:(BFCancellationToken *)token delegate:(BFCancellationBlock)delegate; 27 | 28 | - (void)notifyDelegate; 29 | 30 | @end 31 | 32 | @implementation BFCancellationToken 33 | 34 | @synthesize cancellationRequested = _cancellationRequested; 35 | 36 | #pragma mark - Initializer 37 | 38 | - (instancetype)init { 39 | self = [super init]; 40 | if (!self) return self; 41 | 42 | _registrations = [NSMutableArray array]; 43 | _lock = [NSObject new]; 44 | 45 | return self; 46 | } 47 | 48 | #pragma mark - Custom Setters/Getters 49 | 50 | - (BOOL)isCancellationRequested { 51 | @synchronized(self.lock) { 52 | [self throwIfDisposed]; 53 | return _cancellationRequested; 54 | } 55 | } 56 | 57 | - (void)cancel { 58 | NSArray *registrations; 59 | @synchronized(self.lock) { 60 | [self throwIfDisposed]; 61 | if (_cancellationRequested) { 62 | return; 63 | } 64 | [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(cancelPrivate) object:nil]; 65 | _cancellationRequested = YES; 66 | registrations = [self.registrations copy]; 67 | } 68 | 69 | [self notifyCancellation:registrations]; 70 | } 71 | 72 | - (void)notifyCancellation:(NSArray *)registrations { 73 | for (BFCancellationTokenRegistration *registration in registrations) { 74 | [registration notifyDelegate]; 75 | } 76 | } 77 | 78 | - (BFCancellationTokenRegistration *)registerCancellationObserverWithBlock:(BFCancellationBlock)block { 79 | @synchronized(self.lock) { 80 | BFCancellationTokenRegistration *registration = [BFCancellationTokenRegistration registrationWithToken:self delegate:[block copy]]; 81 | [self.registrations addObject:registration]; 82 | 83 | return registration; 84 | } 85 | } 86 | 87 | - (void)unregisterRegistration:(BFCancellationTokenRegistration *)registration { 88 | @synchronized(self.lock) { 89 | [self throwIfDisposed]; 90 | [self.registrations removeObject:registration]; 91 | } 92 | } 93 | 94 | // Delay on a non-public method to prevent interference with a user calling performSelector or 95 | // cancelPreviousPerformRequestsWithTarget on the public method 96 | - (void)cancelPrivate { 97 | [self cancel]; 98 | } 99 | 100 | - (void)cancelAfterDelay:(int)millis { 101 | [self throwIfDisposed]; 102 | if (millis < -1) { 103 | [NSException raise:NSInvalidArgumentException format:@"Delay must be >= -1"]; 104 | } 105 | 106 | if (millis == 0) { 107 | [self cancel]; 108 | return; 109 | } 110 | 111 | @synchronized(self.lock) { 112 | [self throwIfDisposed]; 113 | [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(cancelPrivate) object:nil]; 114 | if (self.cancellationRequested) { 115 | return; 116 | } 117 | 118 | if (millis != -1) { 119 | double delay = (double)millis / 1000; 120 | [self performSelector:@selector(cancelPrivate) withObject:nil afterDelay:delay]; 121 | } 122 | } 123 | } 124 | 125 | - (void)dispose { 126 | @synchronized(self.lock) { 127 | if (self.disposed) { 128 | return; 129 | } 130 | [self.registrations makeObjectsPerformSelector:@selector(dispose)]; 131 | self.registrations = nil; 132 | self.disposed = YES; 133 | } 134 | } 135 | 136 | - (void)throwIfDisposed { 137 | if (self.disposed) { 138 | [NSException raise:NSInternalInconsistencyException format:@"Object already disposed"]; 139 | } 140 | } 141 | 142 | @end 143 | 144 | NS_ASSUME_NONNULL_END 145 | -------------------------------------------------------------------------------- /Bolts/Common/BFCancellationTokenRegistration.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | /*! 16 | Represents the registration of a cancellation observer with a cancellation token. 17 | Can be used to unregister the observer at a later time. 18 | */ 19 | @interface BFCancellationTokenRegistration : NSObject 20 | 21 | /*! 22 | Removes the cancellation observer registered with the token 23 | and releases all resources associated with this registration. 24 | */ 25 | - (void)dispose; 26 | 27 | @end 28 | 29 | NS_ASSUME_NONNULL_END 30 | -------------------------------------------------------------------------------- /Bolts/Common/BFCancellationTokenRegistration.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import "BFCancellationTokenRegistration.h" 12 | 13 | #import "BFCancellationToken.h" 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @interface BFCancellationTokenRegistration () 18 | 19 | @property (nonatomic, weak) BFCancellationToken *token; 20 | @property (nullable, nonatomic, strong) BFCancellationBlock cancellationObserverBlock; 21 | @property (nonatomic, strong) NSObject *lock; 22 | @property (nonatomic) BOOL disposed; 23 | 24 | @end 25 | 26 | @interface BFCancellationToken (BFCancellationTokenRegistration) 27 | 28 | - (void)unregisterRegistration:(BFCancellationTokenRegistration *)registration; 29 | 30 | @end 31 | 32 | @implementation BFCancellationTokenRegistration 33 | 34 | + (instancetype)registrationWithToken:(BFCancellationToken *)token delegate:(BFCancellationBlock)delegate { 35 | BFCancellationTokenRegistration *registration = [BFCancellationTokenRegistration new]; 36 | registration.token = token; 37 | registration.cancellationObserverBlock = delegate; 38 | return registration; 39 | } 40 | 41 | - (instancetype)init { 42 | self = [super init]; 43 | if (!self) return self; 44 | 45 | _lock = [NSObject new]; 46 | 47 | return self; 48 | } 49 | 50 | - (void)dispose { 51 | @synchronized(self.lock) { 52 | if (self.disposed) { 53 | return; 54 | } 55 | self.disposed = YES; 56 | } 57 | 58 | BFCancellationToken *token = self.token; 59 | if (token != nil) { 60 | [token unregisterRegistration:self]; 61 | self.token = nil; 62 | } 63 | self.cancellationObserverBlock = nil; 64 | } 65 | 66 | - (void)notifyDelegate { 67 | @synchronized(self.lock) { 68 | [self throwIfDisposed]; 69 | self.cancellationObserverBlock(); 70 | } 71 | } 72 | 73 | - (void)throwIfDisposed { 74 | NSAssert(!self.disposed, @"Object already disposed"); 75 | } 76 | 77 | @end 78 | 79 | NS_ASSUME_NONNULL_END 80 | -------------------------------------------------------------------------------- /Bolts/Common/BFCancellationTokenSource.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @class BFCancellationToken; 16 | 17 | /*! 18 | BFCancellationTokenSource represents the producer side of a CancellationToken. 19 | Signals to a CancellationToken that it should be canceled. 20 | It is a cancellation token that also has methods 21 | for changing the state of a token by cancelling it. 22 | */ 23 | @interface BFCancellationTokenSource : NSObject 24 | 25 | /*! 26 | Creates a new cancellation token source. 27 | */ 28 | + (instancetype)cancellationTokenSource; 29 | 30 | /*! 31 | The cancellation token associated with this CancellationTokenSource. 32 | */ 33 | @property (nonatomic, strong, readonly) BFCancellationToken *token; 34 | 35 | /*! 36 | Whether cancellation has been requested for this token source. 37 | */ 38 | @property (nonatomic, assign, readonly, getter=isCancellationRequested) BOOL cancellationRequested; 39 | 40 | /*! 41 | Cancels the token if it has not already been cancelled. 42 | */ 43 | - (void)cancel; 44 | 45 | /*! 46 | Schedules a cancel operation on this CancellationTokenSource after the specified number of milliseconds. 47 | @param millis The number of milliseconds to wait before completing the returned task. 48 | If delay is `0` the cancel is executed immediately. If delay is `-1` any scheduled cancellation is stopped. 49 | */ 50 | - (void)cancelAfterDelay:(int)millis; 51 | 52 | /*! 53 | Releases all resources associated with this token source, 54 | including disposing of all registrations. 55 | */ 56 | - (void)dispose; 57 | 58 | @end 59 | 60 | NS_ASSUME_NONNULL_END 61 | -------------------------------------------------------------------------------- /Bolts/Common/BFCancellationTokenSource.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import "BFCancellationTokenSource.h" 12 | 13 | #import "BFCancellationToken.h" 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @interface BFCancellationToken (BFCancellationTokenSource) 18 | 19 | - (void)cancel; 20 | - (void)cancelAfterDelay:(int)millis; 21 | 22 | - (void)dispose; 23 | - (void)throwIfDisposed; 24 | 25 | @end 26 | 27 | @implementation BFCancellationTokenSource 28 | 29 | #pragma mark - Initializer 30 | 31 | - (instancetype)init { 32 | self = [super init]; 33 | if (!self) return self; 34 | 35 | _token = [BFCancellationToken new]; 36 | 37 | return self; 38 | } 39 | 40 | + (instancetype)cancellationTokenSource { 41 | return [BFCancellationTokenSource new]; 42 | } 43 | 44 | #pragma mark - Custom Setters/Getters 45 | 46 | - (BOOL)isCancellationRequested { 47 | return _token.isCancellationRequested; 48 | } 49 | 50 | - (void)cancel { 51 | [_token cancel]; 52 | } 53 | 54 | - (void)cancelAfterDelay:(int)millis { 55 | [_token cancelAfterDelay:millis]; 56 | } 57 | 58 | - (void)dispose { 59 | [_token dispose]; 60 | } 61 | 62 | @end 63 | 64 | NS_ASSUME_NONNULL_END 65 | -------------------------------------------------------------------------------- /Bolts/Common/BFExecutor.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | /*! 16 | An object that can run a given block. 17 | */ 18 | @interface BFExecutor : NSObject 19 | 20 | /*! 21 | Returns a default executor, which runs continuations immediately until the call stack gets too 22 | deep, then dispatches to a new GCD queue. 23 | */ 24 | + (instancetype)defaultExecutor; 25 | 26 | /*! 27 | Returns an executor that runs continuations on the thread where the previous task was completed. 28 | */ 29 | + (instancetype)immediateExecutor; 30 | 31 | /*! 32 | Returns an executor that runs continuations on the main thread. 33 | */ 34 | + (instancetype)mainThreadExecutor; 35 | 36 | /*! 37 | Returns a new executor that uses the given block to execute continuations. 38 | @param block The block to use. 39 | */ 40 | + (instancetype)executorWithBlock:(void(^)(void(^block)(void)))block; 41 | 42 | /*! 43 | Returns a new executor that runs continuations on the given queue. 44 | @param queue The instance of `dispatch_queue_t` to dispatch all continuations onto. 45 | */ 46 | + (instancetype)executorWithDispatchQueue:(dispatch_queue_t)queue; 47 | 48 | /*! 49 | Returns a new executor that runs continuations on the given queue. 50 | @param queue The instance of `NSOperationQueue` to run all continuations on. 51 | */ 52 | + (instancetype)executorWithOperationQueue:(NSOperationQueue *)queue; 53 | 54 | /*! 55 | Runs the given block using this executor's particular strategy. 56 | @param block The block to execute. 57 | */ 58 | - (void)execute:(void(^)(void))block; 59 | 60 | @end 61 | 62 | NS_ASSUME_NONNULL_END 63 | -------------------------------------------------------------------------------- /Bolts/Common/BFExecutor.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import "BFExecutor.h" 12 | 13 | #import 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | /*! 18 | Get the remaining stack-size of the current thread. 19 | 20 | @param totalSize The total stack size of the current thread. 21 | 22 | @return The remaining size, in bytes, available to the current thread. 23 | 24 | @note This function cannot be inlined, as otherwise the internal implementation could fail to report the proper 25 | remaining stack space. 26 | */ 27 | __attribute__((noinline)) static size_t remaining_stack_size(size_t *restrict totalSize) { 28 | pthread_t currentThread = pthread_self(); 29 | 30 | // NOTE: We must store stack pointers as uint8_t so that the pointer math is well-defined 31 | uint8_t *endStack = pthread_get_stackaddr_np(currentThread); 32 | *totalSize = pthread_get_stacksize_np(currentThread); 33 | 34 | // NOTE: If the function is inlined, this value could be incorrect 35 | uint8_t *frameAddr = __builtin_frame_address(0); 36 | 37 | return (*totalSize) - (size_t)(endStack - frameAddr); 38 | } 39 | 40 | @interface BFExecutor () 41 | 42 | @property (nonatomic, copy) void(^block)(void(^block)(void)); 43 | 44 | @end 45 | 46 | @implementation BFExecutor 47 | 48 | #pragma mark - Executor methods 49 | 50 | + (instancetype)defaultExecutor { 51 | static BFExecutor *defaultExecutor = NULL; 52 | static dispatch_once_t onceToken; 53 | dispatch_once(&onceToken, ^{ 54 | defaultExecutor = [self executorWithBlock:^void(void(^block)(void)) { 55 | // We prefer to run everything possible immediately, so that there is callstack information 56 | // when debugging. However, we don't want the stack to get too deep, so if the remaining stack space 57 | // is less than 10% of the total space, we dispatch to another GCD queue. 58 | size_t totalStackSize = 0; 59 | size_t remainingStackSize = remaining_stack_size(&totalStackSize); 60 | 61 | if (remainingStackSize < (totalStackSize / 10)) { 62 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block); 63 | } else { 64 | @autoreleasepool { 65 | block(); 66 | } 67 | } 68 | }]; 69 | }); 70 | return defaultExecutor; 71 | } 72 | 73 | + (instancetype)immediateExecutor { 74 | static BFExecutor *immediateExecutor = NULL; 75 | static dispatch_once_t onceToken; 76 | dispatch_once(&onceToken, ^{ 77 | immediateExecutor = [self executorWithBlock:^void(void(^block)(void)) { 78 | block(); 79 | }]; 80 | }); 81 | return immediateExecutor; 82 | } 83 | 84 | + (instancetype)mainThreadExecutor { 85 | static BFExecutor *mainThreadExecutor = NULL; 86 | static dispatch_once_t onceToken; 87 | dispatch_once(&onceToken, ^{ 88 | mainThreadExecutor = [self executorWithBlock:^void(void(^block)(void)) { 89 | if (![NSThread isMainThread]) { 90 | dispatch_async(dispatch_get_main_queue(), block); 91 | } else { 92 | @autoreleasepool { 93 | block(); 94 | } 95 | } 96 | }]; 97 | }); 98 | return mainThreadExecutor; 99 | } 100 | 101 | + (instancetype)executorWithBlock:(void(^)(void(^block)(void)))block { 102 | return [[self alloc] initWithBlock:block]; 103 | } 104 | 105 | + (instancetype)executorWithDispatchQueue:(dispatch_queue_t)queue { 106 | return [self executorWithBlock:^void(void(^block)(void)) { 107 | dispatch_async(queue, block); 108 | }]; 109 | } 110 | 111 | + (instancetype)executorWithOperationQueue:(NSOperationQueue *)queue { 112 | return [self executorWithBlock:^void(void(^block)(void)) { 113 | [queue addOperation:[NSBlockOperation blockOperationWithBlock:block]]; 114 | }]; 115 | } 116 | 117 | #pragma mark - Initializer 118 | 119 | - (instancetype)initWithBlock:(void(^)(void(^block)(void)))block { 120 | self = [super init]; 121 | if (!self) return self; 122 | 123 | _block = block; 124 | 125 | return self; 126 | } 127 | 128 | #pragma mark - Execution 129 | 130 | - (void)execute:(void(^)(void))block { 131 | self.block(block); 132 | } 133 | 134 | @end 135 | 136 | NS_ASSUME_NONNULL_END 137 | -------------------------------------------------------------------------------- /Bolts/Common/BFGeneric.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | 13 | #pragma once 14 | 15 | /** 16 | This exists to use along with `BFTask` and `BFTaskCompletionSource`. 17 | 18 | Instead of returning a `BFTask` with no generic type, or a generic type of 'NSNull' 19 | when there is no usable result from a task, we use the type 'BFVoid', which will always have a value of `nil`. 20 | 21 | This allows you to provide a more enforced API contract to the caller, 22 | as sending any message to `BFVoid` will result in a compile time error. 23 | */ 24 | @class _BFVoid_Nonexistant; 25 | typedef _BFVoid_Nonexistant *BFVoid; 26 | -------------------------------------------------------------------------------- /Bolts/Common/BFTask.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | 13 | #import 14 | #import 15 | 16 | NS_ASSUME_NONNULL_BEGIN 17 | 18 | /*! 19 | Error domain used if there was multiple errors on . 20 | */ 21 | extern NSString *const BFTaskErrorDomain; 22 | 23 | /*! 24 | An error code used for , if there were multiple errors. 25 | */ 26 | extern NSInteger const kBFMultipleErrorsError; 27 | 28 | /*! 29 | An error userInfo key used if there were multiple errors on . 30 | Value type is `NSArray *`. 31 | */ 32 | extern NSString *const BFTaskMultipleErrorsUserInfoKey; 33 | 34 | @class BFExecutor; 35 | @class BFTask; 36 | 37 | /*! 38 | The consumer view of a Task. A BFTask has methods to 39 | inspect the state of the task, and to add continuations to 40 | be run once the task is complete. 41 | */ 42 | @interface BFTask<__covariant ResultType> : NSObject 43 | 44 | /*! 45 | A block that can act as a continuation for a task. 46 | */ 47 | typedef __nullable id(^BFContinuationBlock)(BFTask *t); 48 | 49 | /*! 50 | Creates a task that is already completed with the given result. 51 | @param result The result for the task. 52 | */ 53 | + (instancetype)taskWithResult:(nullable ResultType)result; 54 | 55 | /*! 56 | Creates a task that is already completed with the given error. 57 | @param error The error for the task. 58 | */ 59 | + (instancetype)taskWithError:(NSError *)error; 60 | 61 | /*! 62 | Creates a task that is already cancelled. 63 | */ 64 | + (instancetype)cancelledTask; 65 | 66 | /*! 67 | Returns a task that will be completed (with result == nil) once 68 | all of the input tasks have completed. 69 | @param tasks An `NSArray` of the tasks to use as an input. 70 | */ 71 | + (instancetype)taskForCompletionOfAllTasks:(nullable NSArray *)tasks; 72 | 73 | /*! 74 | Returns a task that will be completed once all of the input tasks have completed. 75 | If all tasks complete successfully without being faulted or cancelled the result will be 76 | an `NSArray` of all task results in the order they were provided. 77 | @param tasks An `NSArray` of the tasks to use as an input. 78 | */ 79 | + (instancetype)taskForCompletionOfAllTasksWithResults:(nullable NSArray *)tasks; 80 | 81 | /*! 82 | Returns a task that will be completed once there is at least one successful task. 83 | The first task to successuly complete will set the result, all other tasks results are 84 | ignored. 85 | @param tasks An `NSArray` of the tasks to use as an input. 86 | */ 87 | + (instancetype)taskForCompletionOfAnyTask:(nullable NSArray *)tasks; 88 | 89 | /*! 90 | Returns a task that will be completed a certain amount of time in the future. 91 | @param millis The approximate number of milliseconds to wait before the 92 | task will be finished (with result == nil). 93 | */ 94 | + (BFTask *)taskWithDelay:(int)millis; 95 | 96 | /*! 97 | Returns a task that will be completed a certain amount of time in the future. 98 | @param millis The approximate number of milliseconds to wait before the 99 | task will be finished (with result == nil). 100 | @param token The cancellation token (optional). 101 | */ 102 | + (BFTask *)taskWithDelay:(int)millis cancellationToken:(nullable BFCancellationToken *)token; 103 | 104 | /*! 105 | Returns a task that will be completed after the given block completes with 106 | the specified executor. 107 | @param executor A BFExecutor responsible for determining how the 108 | continuation block will be run. 109 | @param block The block to immediately schedule to run with the given executor. 110 | @returns A task that will be completed after block has run. 111 | If block returns a BFTask, then the task returned from 112 | this method will not be completed until that task is completed. 113 | */ 114 | + (instancetype)taskFromExecutor:(BFExecutor *)executor withBlock:(nullable id (^)(void))block; 115 | 116 | // Properties that will be set on the task once it is completed. 117 | 118 | /*! 119 | The result of a successful task. 120 | */ 121 | @property (nullable, nonatomic, strong, readonly) ResultType result; 122 | 123 | /*! 124 | The error of a failed task. 125 | */ 126 | @property (nullable, nonatomic, strong, readonly) NSError *error; 127 | 128 | /*! 129 | Whether this task has been cancelled. 130 | */ 131 | @property (nonatomic, assign, readonly, getter=isCancelled) BOOL cancelled; 132 | 133 | /*! 134 | Whether this task has completed due to an error. 135 | */ 136 | @property (nonatomic, assign, readonly, getter=isFaulted) BOOL faulted; 137 | 138 | /*! 139 | Whether this task has completed. 140 | */ 141 | @property (nonatomic, assign, readonly, getter=isCompleted) BOOL completed; 142 | 143 | /*! 144 | Enqueues the given block to be run once this task is complete. 145 | This method uses a default execution strategy. The block will be 146 | run on the thread where the previous task completes, unless the 147 | the stack depth is too deep, in which case it will be run on a 148 | dispatch queue with default priority. 149 | @param block The block to be run once this task is complete. 150 | @returns A task that will be completed after block has run. 151 | If block returns a BFTask, then the task returned from 152 | this method will not be completed until that task is completed. 153 | */ 154 | - (BFTask *)continueWithBlock:(BFContinuationBlock)block NS_SWIFT_NAME(continueWith(block:)); 155 | 156 | /*! 157 | Enqueues the given block to be run once this task is complete. 158 | This method uses a default execution strategy. The block will be 159 | run on the thread where the previous task completes, unless the 160 | the stack depth is too deep, in which case it will be run on a 161 | dispatch queue with default priority. 162 | @param block The block to be run once this task is complete. 163 | @param cancellationToken The cancellation token (optional). 164 | @returns A task that will be completed after block has run. 165 | If block returns a BFTask, then the task returned from 166 | this method will not be completed until that task is completed. 167 | */ 168 | - (BFTask *)continueWithBlock:(BFContinuationBlock)block 169 | cancellationToken:(nullable BFCancellationToken *)cancellationToken NS_SWIFT_NAME(continueWith(block:cancellationToken:)); 170 | 171 | /*! 172 | Enqueues the given block to be run once this task is complete. 173 | @param executor A BFExecutor responsible for determining how the 174 | continuation block will be run. 175 | @param block The block to be run once this task is complete. 176 | @returns A task that will be completed after block has run. 177 | If block returns a BFTask, then the task returned from 178 | this method will not be completed until that task is completed. 179 | */ 180 | - (BFTask *)continueWithExecutor:(BFExecutor *)executor 181 | withBlock:(BFContinuationBlock)block NS_SWIFT_NAME(continueWith(executor:block:)); 182 | 183 | /*! 184 | Enqueues the given block to be run once this task is complete. 185 | @param executor A BFExecutor responsible for determining how the 186 | continuation block will be run. 187 | @param block The block to be run once this task is complete. 188 | @param cancellationToken The cancellation token (optional). 189 | @returns A task that will be completed after block has run. 190 | If block returns a BFTask, then the task returned from 191 | his method will not be completed until that task is completed. 192 | */ 193 | - (BFTask *)continueWithExecutor:(BFExecutor *)executor 194 | block:(BFContinuationBlock)block 195 | cancellationToken:(nullable BFCancellationToken *)cancellationToken 196 | NS_SWIFT_NAME(continueWith(executor:block:cancellationToken:)); 197 | 198 | /*! 199 | Identical to continueWithBlock:, except that the block is only run 200 | if this task did not produce a cancellation or an error. 201 | If it did, then the failure will be propagated to the returned 202 | task. 203 | @param block The block to be run once this task is complete. 204 | @returns A task that will be completed after block has run. 205 | If block returns a BFTask, then the task returned from 206 | this method will not be completed until that task is completed. 207 | */ 208 | - (BFTask *)continueWithSuccessBlock:(BFContinuationBlock)block NS_SWIFT_NAME(continueOnSuccessWith(block:)); 209 | 210 | /*! 211 | Identical to continueWithBlock:, except that the block is only run 212 | if this task did not produce a cancellation or an error. 213 | If it did, then the failure will be propagated to the returned 214 | task. 215 | @param block The block to be run once this task is complete. 216 | @param cancellationToken The cancellation token (optional). 217 | @returns A task that will be completed after block has run. 218 | If block returns a BFTask, then the task returned from 219 | this method will not be completed until that task is completed. 220 | */ 221 | - (BFTask *)continueWithSuccessBlock:(BFContinuationBlock)block 222 | cancellationToken:(nullable BFCancellationToken *)cancellationToken 223 | NS_SWIFT_NAME(continueOnSuccessWith(block:cancellationToken:)); 224 | 225 | /*! 226 | Identical to continueWithExecutor:withBlock:, except that the block 227 | is only run if this task did not produce a cancellation, error, or an error. 228 | If it did, then the failure will be propagated to the returned task. 229 | @param executor A BFExecutor responsible for determining how the 230 | continuation block will be run. 231 | @param block The block to be run once this task is complete. 232 | @returns A task that will be completed after block has run. 233 | If block returns a BFTask, then the task returned from 234 | this method will not be completed until that task is completed. 235 | */ 236 | - (BFTask *)continueWithExecutor:(BFExecutor *)executor 237 | withSuccessBlock:(BFContinuationBlock)block NS_SWIFT_NAME(continueOnSuccessWith(executor:block:)); 238 | 239 | /*! 240 | Identical to continueWithExecutor:withBlock:, except that the block 241 | is only run if this task did not produce a cancellation or an error. 242 | If it did, then the failure will be propagated to the returned task. 243 | @param executor A BFExecutor responsible for determining how the 244 | continuation block will be run. 245 | @param block The block to be run once this task is complete. 246 | @param cancellationToken The cancellation token (optional). 247 | @returns A task that will be completed after block has run. 248 | If block returns a BFTask, then the task returned from 249 | this method will not be completed until that task is completed. 250 | */ 251 | - (BFTask *)continueWithExecutor:(BFExecutor *)executor 252 | successBlock:(BFContinuationBlock)block 253 | cancellationToken:(nullable BFCancellationToken *)cancellationToken 254 | NS_SWIFT_NAME(continueOnSuccessWith(executor:block:cancellationToken:)); 255 | 256 | /*! 257 | Waits until this operation is completed. 258 | This method is inefficient and consumes a thread resource while 259 | it's running. It should be avoided. This method logs a warning 260 | message if it is used on the main thread. 261 | */ 262 | - (void)waitUntilFinished; 263 | 264 | @end 265 | 266 | NS_ASSUME_NONNULL_END 267 | -------------------------------------------------------------------------------- /Bolts/Common/BFTask.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import "BFTask.h" 12 | 13 | #import 14 | 15 | #import "Bolts.h" 16 | 17 | NS_ASSUME_NONNULL_BEGIN 18 | 19 | __attribute__ ((noinline)) void warnBlockingOperationOnMainThread() { 20 | NSLog(@"Warning: A long-running operation is being executed on the main thread. \n" 21 | " Break on warnBlockingOperationOnMainThread() to debug."); 22 | } 23 | 24 | NSString *const BFTaskErrorDomain = @"bolts"; 25 | NSInteger const kBFMultipleErrorsError = 80175001; 26 | 27 | NSString *const BFTaskMultipleErrorsUserInfoKey = @"errors"; 28 | 29 | @interface BFTask () { 30 | id _result; 31 | NSError *_error; 32 | } 33 | 34 | @property (nonatomic, assign, readwrite, getter=isCancelled) BOOL cancelled; 35 | @property (nonatomic, assign, readwrite, getter=isFaulted) BOOL faulted; 36 | @property (nonatomic, assign, readwrite, getter=isCompleted) BOOL completed; 37 | 38 | @property (nonatomic, strong) NSObject *lock; 39 | @property (nonatomic, strong) NSCondition *condition; 40 | @property (nonatomic, strong) NSMutableArray *callbacks; 41 | 42 | @end 43 | 44 | @implementation BFTask 45 | 46 | #pragma mark - Initializer 47 | 48 | - (instancetype)init { 49 | self = [super init]; 50 | if (!self) return self; 51 | 52 | _lock = [[NSObject alloc] init]; 53 | _condition = [[NSCondition alloc] init]; 54 | _callbacks = [NSMutableArray array]; 55 | 56 | return self; 57 | } 58 | 59 | - (instancetype)initWithResult:(nullable id)result { 60 | self = [super init]; 61 | if (!self) return self; 62 | 63 | [self trySetResult:result]; 64 | 65 | return self; 66 | } 67 | 68 | - (instancetype)initWithError:(NSError *)error { 69 | self = [super init]; 70 | if (!self) return self; 71 | 72 | [self trySetError:error]; 73 | 74 | return self; 75 | } 76 | 77 | - (instancetype)initCancelled { 78 | self = [super init]; 79 | if (!self) return self; 80 | 81 | [self trySetCancelled]; 82 | 83 | return self; 84 | } 85 | 86 | #pragma mark - Task Class methods 87 | 88 | + (instancetype)taskWithResult:(nullable id)result { 89 | return [[self alloc] initWithResult:result]; 90 | } 91 | 92 | + (instancetype)taskWithError:(NSError *)error { 93 | return [[self alloc] initWithError:error]; 94 | } 95 | 96 | + (instancetype)cancelledTask { 97 | return [[self alloc] initCancelled]; 98 | } 99 | 100 | + (instancetype)taskForCompletionOfAllTasks:(nullable NSArray *)tasks { 101 | __block int32_t total = (int32_t)tasks.count; 102 | if (total == 0) { 103 | return [self taskWithResult:nil]; 104 | } 105 | 106 | __block int32_t cancelled = 0; 107 | NSObject *lock = [[NSObject alloc] init]; 108 | NSMutableArray *errors = [NSMutableArray array]; 109 | 110 | BFTaskCompletionSource *tcs = [BFTaskCompletionSource taskCompletionSource]; 111 | for (BFTask *task in tasks) { 112 | [task continueWithBlock:^id(BFTask *t) { 113 | if (t.error) { 114 | @synchronized (lock) { 115 | [errors addObject:t.error]; 116 | } 117 | } else if (t.cancelled) { 118 | OSAtomicIncrement32Barrier(&cancelled); 119 | } 120 | 121 | if (OSAtomicDecrement32Barrier(&total) == 0) { 122 | if (errors.count > 0) { 123 | if (errors.count == 1) { 124 | tcs.error = [errors firstObject]; 125 | } else { 126 | NSError *error = [NSError errorWithDomain:BFTaskErrorDomain 127 | code:kBFMultipleErrorsError 128 | userInfo:@{ BFTaskMultipleErrorsUserInfoKey: errors }]; 129 | tcs.error = error; 130 | } 131 | } else if (cancelled > 0) { 132 | [tcs cancel]; 133 | } else { 134 | tcs.result = nil; 135 | } 136 | } 137 | return nil; 138 | }]; 139 | } 140 | return tcs.task; 141 | } 142 | 143 | + (instancetype)taskForCompletionOfAllTasksWithResults:(nullable NSArray *)tasks { 144 | return [[self taskForCompletionOfAllTasks:tasks] continueWithSuccessBlock:^id(BFTask * __unused task) { 145 | return [tasks valueForKey:@"result"]; 146 | }]; 147 | } 148 | 149 | + (instancetype)taskForCompletionOfAnyTask:(nullable NSArray *)tasks 150 | { 151 | __block int32_t total = (int32_t)tasks.count; 152 | if (total == 0) { 153 | return [self taskWithResult:nil]; 154 | } 155 | 156 | __block int completed = 0; 157 | __block int32_t cancelled = 0; 158 | 159 | NSObject *lock = [NSObject new]; 160 | NSMutableArray *errors = [NSMutableArray new]; 161 | 162 | BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource]; 163 | for (BFTask *task in tasks) { 164 | [task continueWithBlock:^id(BFTask *t) { 165 | if (t.error != nil) { 166 | @synchronized(lock) { 167 | [errors addObject:t.error]; 168 | } 169 | } else if (t.cancelled) { 170 | OSAtomicIncrement32Barrier(&cancelled); 171 | } else { 172 | if(OSAtomicCompareAndSwap32Barrier(0, 1, &completed)) { 173 | [source setResult:t.result]; 174 | } 175 | } 176 | 177 | if (OSAtomicDecrement32Barrier(&total) == 0 && 178 | OSAtomicCompareAndSwap32Barrier(0, 1, &completed)) { 179 | if (cancelled > 0) { 180 | [source cancel]; 181 | } else if (errors.count > 0) { 182 | if (errors.count == 1) { 183 | source.error = errors.firstObject; 184 | } else { 185 | NSError *error = [NSError errorWithDomain:BFTaskErrorDomain 186 | code:kBFMultipleErrorsError 187 | userInfo:@{ @"errors": errors }]; 188 | source.error = error; 189 | } 190 | } 191 | } 192 | // Abort execution of per tasks continuations 193 | return nil; 194 | }]; 195 | } 196 | return source.task; 197 | } 198 | 199 | 200 | + (BFTask *)taskWithDelay:(int)millis { 201 | BFTaskCompletionSource *tcs = [BFTaskCompletionSource taskCompletionSource]; 202 | dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, millis * NSEC_PER_MSEC); 203 | dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){ 204 | tcs.result = nil; 205 | }); 206 | return tcs.task; 207 | } 208 | 209 | + (BFTask *)taskWithDelay:(int)millis cancellationToken:(nullable BFCancellationToken *)token { 210 | if (token.cancellationRequested) { 211 | return [BFTask cancelledTask]; 212 | } 213 | 214 | BFTaskCompletionSource *tcs = [BFTaskCompletionSource taskCompletionSource]; 215 | dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, millis * NSEC_PER_MSEC); 216 | dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){ 217 | if (token.cancellationRequested) { 218 | [tcs cancel]; 219 | return; 220 | } 221 | tcs.result = nil; 222 | }); 223 | return tcs.task; 224 | } 225 | 226 | + (instancetype)taskFromExecutor:(BFExecutor *)executor withBlock:(nullable id (^)(void))block { 227 | return [[self taskWithResult:nil] continueWithExecutor:executor withBlock:^id(BFTask *task) { 228 | return block(); 229 | }]; 230 | } 231 | 232 | #pragma mark - Custom Setters/Getters 233 | 234 | - (nullable id)result { 235 | @synchronized(self.lock) { 236 | return _result; 237 | } 238 | } 239 | 240 | - (BOOL)trySetResult:(nullable id)result { 241 | @synchronized(self.lock) { 242 | if (self.completed) { 243 | return NO; 244 | } 245 | self.completed = YES; 246 | _result = result; 247 | [self runContinuations]; 248 | return YES; 249 | } 250 | } 251 | 252 | - (nullable NSError *)error { 253 | @synchronized(self.lock) { 254 | return _error; 255 | } 256 | } 257 | 258 | - (BOOL)trySetError:(NSError *)error { 259 | @synchronized(self.lock) { 260 | if (self.completed) { 261 | return NO; 262 | } 263 | self.completed = YES; 264 | self.faulted = YES; 265 | _error = error; 266 | [self runContinuations]; 267 | return YES; 268 | } 269 | } 270 | 271 | - (BOOL)isCancelled { 272 | @synchronized(self.lock) { 273 | return _cancelled; 274 | } 275 | } 276 | 277 | - (BOOL)isFaulted { 278 | @synchronized(self.lock) { 279 | return _faulted; 280 | } 281 | } 282 | 283 | - (BOOL)trySetCancelled { 284 | @synchronized(self.lock) { 285 | if (self.completed) { 286 | return NO; 287 | } 288 | self.completed = YES; 289 | self.cancelled = YES; 290 | [self runContinuations]; 291 | return YES; 292 | } 293 | } 294 | 295 | - (BOOL)isCompleted { 296 | @synchronized(self.lock) { 297 | return _completed; 298 | } 299 | } 300 | 301 | - (void)runContinuations { 302 | @synchronized(self.lock) { 303 | [self.condition lock]; 304 | [self.condition broadcast]; 305 | [self.condition unlock]; 306 | for (void (^callback)(void) in self.callbacks) { 307 | callback(); 308 | } 309 | [self.callbacks removeAllObjects]; 310 | } 311 | } 312 | 313 | #pragma mark - Chaining methods 314 | 315 | - (BFTask *)continueWithExecutor:(BFExecutor *)executor withBlock:(BFContinuationBlock)block { 316 | return [self continueWithExecutor:executor block:block cancellationToken:nil]; 317 | } 318 | 319 | - (BFTask *)continueWithExecutor:(BFExecutor *)executor 320 | block:(BFContinuationBlock)block 321 | cancellationToken:(nullable BFCancellationToken *)cancellationToken { 322 | BFTaskCompletionSource *tcs = [BFTaskCompletionSource taskCompletionSource]; 323 | 324 | // Capture all of the state that needs to used when the continuation is complete. 325 | dispatch_block_t executionBlock = ^{ 326 | if (cancellationToken.cancellationRequested) { 327 | [tcs cancel]; 328 | return; 329 | } 330 | 331 | id result = block(self); 332 | if ([result isKindOfClass:[BFTask class]]) { 333 | 334 | id (^setupWithTask) (BFTask *) = ^id(BFTask *task) { 335 | if (cancellationToken.cancellationRequested || task.cancelled) { 336 | [tcs cancel]; 337 | } else if (task.error) { 338 | tcs.error = task.error; 339 | } else { 340 | tcs.result = task.result; 341 | } 342 | return nil; 343 | }; 344 | 345 | BFTask *resultTask = (BFTask *)result; 346 | 347 | if (resultTask.completed) { 348 | setupWithTask(resultTask); 349 | } else { 350 | [resultTask continueWithBlock:setupWithTask]; 351 | } 352 | 353 | } else { 354 | tcs.result = result; 355 | } 356 | }; 357 | 358 | BOOL completed; 359 | @synchronized(self.lock) { 360 | completed = self.completed; 361 | if (!completed) { 362 | [self.callbacks addObject:[^{ 363 | [executor execute:executionBlock]; 364 | } copy]]; 365 | } 366 | } 367 | if (completed) { 368 | [executor execute:executionBlock]; 369 | } 370 | 371 | return tcs.task; 372 | } 373 | 374 | - (BFTask *)continueWithBlock:(BFContinuationBlock)block { 375 | return [self continueWithExecutor:[BFExecutor defaultExecutor] block:block cancellationToken:nil]; 376 | } 377 | 378 | - (BFTask *)continueWithBlock:(BFContinuationBlock)block cancellationToken:(nullable BFCancellationToken *)cancellationToken { 379 | return [self continueWithExecutor:[BFExecutor defaultExecutor] block:block cancellationToken:cancellationToken]; 380 | } 381 | 382 | - (BFTask *)continueWithExecutor:(BFExecutor *)executor 383 | withSuccessBlock:(BFContinuationBlock)block { 384 | return [self continueWithExecutor:executor successBlock:block cancellationToken:nil]; 385 | } 386 | 387 | - (BFTask *)continueWithExecutor:(BFExecutor *)executor 388 | successBlock:(BFContinuationBlock)block 389 | cancellationToken:(nullable BFCancellationToken *)cancellationToken { 390 | if (cancellationToken.cancellationRequested) { 391 | return [BFTask cancelledTask]; 392 | } 393 | 394 | return [self continueWithExecutor:executor block:^id(BFTask *task) { 395 | if (task.faulted || task.cancelled) { 396 | return task; 397 | } else { 398 | return block(task); 399 | } 400 | } cancellationToken:cancellationToken]; 401 | } 402 | 403 | - (BFTask *)continueWithSuccessBlock:(BFContinuationBlock)block { 404 | return [self continueWithExecutor:[BFExecutor defaultExecutor] successBlock:block cancellationToken:nil]; 405 | } 406 | 407 | - (BFTask *)continueWithSuccessBlock:(BFContinuationBlock)block cancellationToken:(nullable BFCancellationToken *)cancellationToken { 408 | return [self continueWithExecutor:[BFExecutor defaultExecutor] successBlock:block cancellationToken:cancellationToken]; 409 | } 410 | 411 | #pragma mark - Syncing Task (Avoid it) 412 | 413 | - (void)warnOperationOnMainThread { 414 | warnBlockingOperationOnMainThread(); 415 | } 416 | 417 | - (void)waitUntilFinished { 418 | if ([NSThread isMainThread]) { 419 | [self warnOperationOnMainThread]; 420 | } 421 | 422 | @synchronized(self.lock) { 423 | if (self.completed) { 424 | return; 425 | } 426 | [self.condition lock]; 427 | } 428 | // TODO: (nlutsenko) Restructure this to use Bolts-Swift thread access synchronization architecture 429 | // In the meantime, it's absolutely safe to get `_completed` aka an ivar, as long as it's a `BOOL` aka less than word size. 430 | while (!_completed) { 431 | [self.condition wait]; 432 | } 433 | [self.condition unlock]; 434 | } 435 | 436 | #pragma mark - NSObject 437 | 438 | - (NSString *)description { 439 | // Acquire the data from the locked properties 440 | BOOL completed; 441 | BOOL cancelled; 442 | BOOL faulted; 443 | NSString *resultDescription = nil; 444 | 445 | @synchronized(self.lock) { 446 | completed = self.completed; 447 | cancelled = self.cancelled; 448 | faulted = self.faulted; 449 | resultDescription = completed ? [NSString stringWithFormat:@" result = %@", self.result] : @""; 450 | } 451 | 452 | // Description string includes status information and, if available, the 453 | // result since in some ways this is what a promise actually "is". 454 | return [NSString stringWithFormat:@"<%@: %p; completed = %@; cancelled = %@; faulted = %@;%@>", 455 | NSStringFromClass([self class]), 456 | self, 457 | completed ? @"YES" : @"NO", 458 | cancelled ? @"YES" : @"NO", 459 | faulted ? @"YES" : @"NO", 460 | resultDescription]; 461 | } 462 | 463 | @end 464 | 465 | NS_ASSUME_NONNULL_END 466 | -------------------------------------------------------------------------------- /Bolts/Common/BFTaskCompletionSource.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @class BFTask<__covariant ResultType>; 16 | 17 | /*! 18 | A BFTaskCompletionSource represents the producer side of tasks. 19 | It is a task that also has methods for changing the state of the 20 | task by settings its completion values. 21 | */ 22 | @interface BFTaskCompletionSource<__covariant ResultType> : NSObject 23 | 24 | /*! 25 | Creates a new unfinished task. 26 | */ 27 | + (instancetype)taskCompletionSource; 28 | 29 | /*! 30 | The task associated with this TaskCompletionSource. 31 | */ 32 | @property (nonatomic, strong, readonly) BFTask *task; 33 | 34 | /*! 35 | Completes the task by setting the result. 36 | Attempting to set this for a completed task will raise an exception. 37 | @param result The result of the task. 38 | */ 39 | - (void)setResult:(nullable ResultType)result NS_SWIFT_NAME(set(result:)); 40 | 41 | /*! 42 | Completes the task by setting the error. 43 | Attempting to set this for a completed task will raise an exception. 44 | @param error The error for the task. 45 | */ 46 | - (void)setError:(NSError *)error NS_SWIFT_NAME(set(error:)); 47 | 48 | /*! 49 | Completes the task by marking it as cancelled. 50 | Attempting to set this for a completed task will raise an exception. 51 | */ 52 | - (void)cancel; 53 | 54 | /*! 55 | Sets the result of the task if it wasn't already completed. 56 | @returns whether the new value was set. 57 | */ 58 | - (BOOL)trySetResult:(nullable ResultType)result NS_SWIFT_NAME(trySet(result:)); 59 | 60 | /*! 61 | Sets the error of the task if it wasn't already completed. 62 | @param error The error for the task. 63 | @returns whether the new value was set. 64 | */ 65 | - (BOOL)trySetError:(NSError *)error NS_SWIFT_NAME(trySet(error:)); 66 | 67 | /*! 68 | Sets the cancellation state of the task if it wasn't already completed. 69 | @returns whether the new value was set. 70 | */ 71 | - (BOOL)trySetCancelled; 72 | 73 | @end 74 | 75 | NS_ASSUME_NONNULL_END 76 | -------------------------------------------------------------------------------- /Bolts/Common/BFTaskCompletionSource.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import "BFTaskCompletionSource.h" 12 | 13 | #import "BFTask.h" 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @interface BFTask (BFTaskCompletionSource) 18 | 19 | - (BOOL)trySetResult:(nullable id)result; 20 | - (BOOL)trySetError:(NSError *)error; 21 | - (BOOL)trySetCancelled; 22 | 23 | @end 24 | 25 | @implementation BFTaskCompletionSource 26 | 27 | #pragma mark - Initializer 28 | 29 | + (instancetype)taskCompletionSource { 30 | return [[self alloc] init]; 31 | } 32 | 33 | - (instancetype)init { 34 | self = [super init]; 35 | if (!self) return self; 36 | 37 | _task = [[BFTask alloc] init]; 38 | 39 | return self; 40 | } 41 | 42 | #pragma mark - Custom Setters/Getters 43 | 44 | - (void)setResult:(nullable id)result { 45 | if (![self.task trySetResult:result]) { 46 | [NSException raise:NSInternalInconsistencyException 47 | format:@"Cannot set the result on a completed task."]; 48 | } 49 | } 50 | 51 | - (void)setError:(NSError *)error { 52 | if (![self.task trySetError:error]) { 53 | [NSException raise:NSInternalInconsistencyException 54 | format:@"Cannot set the error on a completed task."]; 55 | } 56 | } 57 | 58 | - (void)cancel { 59 | if (![self.task trySetCancelled]) { 60 | [NSException raise:NSInternalInconsistencyException 61 | format:@"Cannot cancel a completed task."]; 62 | } 63 | } 64 | 65 | - (BOOL)trySetResult:(nullable id)result { 66 | return [self.task trySetResult:result]; 67 | } 68 | 69 | - (BOOL)trySetError:(NSError *)error { 70 | return [self.task trySetError:error]; 71 | } 72 | 73 | - (BOOL)trySetCancelled { 74 | return [self.task trySetCancelled]; 75 | } 76 | 77 | @end 78 | 79 | NS_ASSUME_NONNULL_END 80 | -------------------------------------------------------------------------------- /Bolts/Common/Bolts.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | #import 13 | #import 14 | #import 15 | #import 16 | #import 17 | #import 18 | 19 | #if __has_include() && TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV 20 | #import 21 | #import 22 | #import 23 | #import 24 | #import 25 | #import 26 | #import 27 | #import 28 | #import 29 | #endif 30 | 31 | 32 | NS_ASSUME_NONNULL_BEGIN 33 | 34 | /** 35 | A string containing the version of the Bolts Framework used by the current application. 36 | */ 37 | extern NSString *const BoltsFrameworkVersionString; 38 | 39 | NS_ASSUME_NONNULL_END 40 | -------------------------------------------------------------------------------- /Bolts/Common/Bolts.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import "Bolts.h" 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | NSString *const BoltsFrameworkVersionString = @"1.9.0"; 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Bolts/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | $(BOLTS_OBJC_VERSION) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(BOLTS_OBJC_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Bolts/Resources/iOS.modulemap: -------------------------------------------------------------------------------- 1 | framework module Bolts { 2 | umbrella header "Bolts.h" 3 | 4 | export * 5 | module * { export * } 6 | 7 | explicit module BFAppLinkResolving { 8 | header "BFAppLinkResolving.h" 9 | export * 10 | } 11 | explicit module BFWebViewAppLinkResolver { 12 | header "BFWebViewAppLinkResolver.h" 13 | export * 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Bolts/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoltsFramework/Bolts-ObjC/b3ad4a4ee5b5b115e03be28df8b283afa284de3d/Bolts/en.lproj/Localizable.strings -------------------------------------------------------------------------------- /Bolts/iOS/BFAppLink.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | 13 | /*! The version of the App Link protocol that this library supports */ 14 | FOUNDATION_EXPORT NSString *const BFAppLinkVersion; 15 | 16 | /*! 17 | Contains App Link metadata relevant for navigation on this device 18 | derived from the HTML at a given URL. 19 | */ 20 | @interface BFAppLink : NSObject 21 | 22 | /*! 23 | Creates a BFAppLink with the given list of BFAppLinkTargets and target URL. 24 | 25 | Generally, this will only be used by implementers of the BFAppLinkResolving protocol, 26 | as these implementers will produce App Link metadata for a given URL. 27 | 28 | @param sourceURL the URL from which this App Link is derived 29 | @param targets an ordered list of BFAppLinkTargets for this platform derived 30 | from App Link metadata. 31 | @param webURL the fallback web URL, if any, for the app link. 32 | */ 33 | + (instancetype)appLinkWithSourceURL:(NSURL *)sourceURL 34 | targets:(NSArray *)targets 35 | webURL:(NSURL *)webURL; 36 | 37 | /*! The URL from which this BFAppLink was derived */ 38 | @property (nonatomic, strong, readonly) NSURL *sourceURL; 39 | 40 | /*! 41 | The ordered list of targets applicable to this platform that will be used 42 | for navigation. 43 | */ 44 | @property (nonatomic, copy, readonly) NSArray *targets; 45 | 46 | /*! The fallback web URL to use if no targets are installed on this device. */ 47 | @property (nonatomic, strong, readonly) NSURL *webURL; 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /Bolts/iOS/BFAppLink.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import "BFAppLink_Internal.h" 12 | 13 | NSString *const BFAppLinkDataParameterName = @"al_applink_data"; 14 | NSString *const BFAppLinkTargetKeyName = @"target_url"; 15 | NSString *const BFAppLinkUserAgentKeyName = @"user_agent"; 16 | NSString *const BFAppLinkExtrasKeyName = @"extras"; 17 | NSString *const BFAppLinkRefererAppLink = @"referer_app_link"; 18 | NSString *const BFAppLinkRefererAppName = @"app_name"; 19 | NSString *const BFAppLinkRefererUrl = @"url"; 20 | NSString *const BFAppLinkVersionKeyName = @"version"; 21 | NSString *const BFAppLinkVersion = @"1.0"; 22 | 23 | @interface BFAppLink () 24 | 25 | @property (nonatomic, strong, readwrite) NSURL *sourceURL; 26 | @property (nonatomic, copy, readwrite) NSArray *targets; 27 | @property (nonatomic, strong, readwrite) NSURL *webURL; 28 | 29 | @property (nonatomic, assign, readwrite, getter=isBackToReferrer) BOOL backToReferrer; 30 | 31 | @end 32 | 33 | @implementation BFAppLink 34 | 35 | + (instancetype)appLinkWithSourceURL:(NSURL *)sourceURL 36 | targets:(NSArray *)targets 37 | webURL:(NSURL *)webURL 38 | isBackToReferrer:(BOOL)isBackToReferrer { 39 | BFAppLink *link = [[self alloc] initWithIsBackToReferrer:isBackToReferrer]; 40 | link.sourceURL = sourceURL; 41 | link.targets = [targets copy]; 42 | link.webURL = webURL; 43 | return link; 44 | } 45 | 46 | + (instancetype)appLinkWithSourceURL:(NSURL *)sourceURL 47 | targets:(NSArray *)targets 48 | webURL:(NSURL *)webURL { 49 | return [self appLinkWithSourceURL:sourceURL 50 | targets:targets 51 | webURL:webURL 52 | isBackToReferrer:NO]; 53 | } 54 | 55 | - (BFAppLink *)initWithIsBackToReferrer:(BOOL)backToReferrer { 56 | if ((self = [super init])) { 57 | _backToReferrer = backToReferrer; 58 | } 59 | return self; 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /Bolts/iOS/BFAppLinkNavigation.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | 13 | #import 14 | 15 | /*! 16 | The result of calling navigate on a BFAppLinkNavigation 17 | */ 18 | typedef NS_ENUM(NSInteger, BFAppLinkNavigationType) { 19 | /*! Indicates that the navigation failed and no app was opened */ 20 | BFAppLinkNavigationTypeFailure, 21 | /*! Indicates that the navigation succeeded by opening the URL in the browser */ 22 | BFAppLinkNavigationTypeBrowser, 23 | /*! Indicates that the navigation succeeded by opening the URL in an app on the device */ 24 | BFAppLinkNavigationTypeApp 25 | }; 26 | 27 | @protocol BFAppLinkResolving; 28 | @class BFTask; 29 | 30 | /*! 31 | Represents a pending request to navigate to an App Link. Most developers will 32 | simply use navigateToURLInBackground: to open a URL, but developers can build 33 | custom requests with additional navigation and app data attached to them by 34 | creating BFAppLinkNavigations themselves. 35 | */ 36 | NS_EXTENSION_UNAVAILABLE_IOS("Not available in app extension") 37 | @interface BFAppLinkNavigation : NSObject 38 | 39 | /*! 40 | The extras for the AppLinkNavigation. This will generally contain application-specific 41 | data that should be passed along with the request, such as advertiser or affiliate IDs or 42 | other such metadata relevant on this device. 43 | */ 44 | @property (nonatomic, copy, readonly) NSDictionary *extras; 45 | 46 | /*! 47 | The al_applink_data for the AppLinkNavigation. This will generally contain data common to 48 | navigation attempts such as back-links, user agents, and other information that may be used 49 | in routing and handling an App Link request. 50 | */ 51 | @property (nonatomic, copy, readonly) NSDictionary *appLinkData; 52 | 53 | /*! The AppLink to navigate to */ 54 | @property (nonatomic, strong, readonly) BFAppLink *appLink; 55 | 56 | /*! Creates an AppLinkNavigation with the given link, extras, and App Link data */ 57 | + (instancetype)navigationWithAppLink:(BFAppLink *)appLink 58 | extras:(NSDictionary *)extras 59 | appLinkData:(NSDictionary *)appLinkData; 60 | 61 | /*! 62 | Creates an NSDictionary with the correct format for iOS callback URLs, 63 | to be used as 'appLinkData' argument in the call to navigationWithAppLink:extras:appLinkData: 64 | */ 65 | + (NSDictionary *)callbackAppLinkDataForAppWithName:(NSString *)appName url:(NSString *)url; 66 | 67 | /*! Performs the navigation */ 68 | - (BFAppLinkNavigationType)navigate:(NSError **)error; 69 | 70 | /*! Returns a BFAppLink for the given URL */ 71 | + (BFTask *)resolveAppLinkInBackground:(NSURL *)destination; 72 | 73 | /*! Returns a BFAppLink for the given URL using the given App Link resolution strategy */ 74 | + (BFTask *)resolveAppLinkInBackground:(NSURL *)destination resolver:(id)resolver; 75 | 76 | /*! Navigates to a BFAppLink and returns whether it opened in-app or in-browser */ 77 | + (BFAppLinkNavigationType)navigateToAppLink:(BFAppLink *)link error:(NSError **)error; 78 | 79 | /*! 80 | Returns a BFAppLinkNavigationType based on a BFAppLink. 81 | It's essentially a no-side-effect version of navigateToAppLink:error:, 82 | allowing apps to determine flow based on the link type (e.g. open an 83 | internal web view instead of going straight to the browser for regular links.) 84 | */ 85 | + (BFAppLinkNavigationType)navigationTypeForLink:(BFAppLink *)link; 86 | 87 | /*! 88 | Return navigation type for current instance. 89 | No-side-effect version of navigate: 90 | */ 91 | - (BFAppLinkNavigationType)navigationType; 92 | 93 | /*! Navigates to a URL (an asynchronous action) and returns a BFNavigationType */ 94 | + (BFTask *)navigateToURLInBackground:(NSURL *)destination; 95 | 96 | /*! 97 | Navigates to a URL (an asynchronous action) using the given App Link resolution 98 | strategy and returns a BFNavigationType 99 | */ 100 | + (BFTask *)navigateToURLInBackground:(NSURL *)destination resolver:(id)resolver; 101 | 102 | /*! 103 | Gets the default resolver to be used for App Link resolution. If the developer has not set one explicitly, 104 | a basic, built-in resolver will be used. 105 | */ 106 | + (id)defaultResolver; 107 | 108 | /*! 109 | Sets the default resolver to be used for App Link resolution. Setting this to nil will revert the 110 | default resolver to the basic, built-in resolver provided by Bolts. 111 | */ 112 | + (void)setDefaultResolver:(id)resolver; 113 | 114 | @end 115 | -------------------------------------------------------------------------------- /Bolts/iOS/BFAppLinkNavigation.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import "BFAppLinkNavigation.h" 12 | 13 | #import 14 | 15 | #import "BFMeasurementEvent_Internal.h" 16 | #import "BFAppLink_Internal.h" 17 | 18 | FOUNDATION_EXPORT NSString *const BFAppLinkDataParameterName; 19 | FOUNDATION_EXPORT NSString *const BFAppLinkTargetKeyName; 20 | FOUNDATION_EXPORT NSString *const BFAppLinkUserAgentKeyName; 21 | FOUNDATION_EXPORT NSString *const BFAppLinkExtrasKeyName; 22 | FOUNDATION_EXPORT NSString *const BFAppLinkVersionKeyName; 23 | FOUNDATION_EXPORT NSString *const BFAppLinkRefererAppLink; 24 | FOUNDATION_EXPORT NSString *const BFAppLinkRefererAppName; 25 | FOUNDATION_EXPORT NSString *const BFAppLinkRefererUrl; 26 | 27 | static id defaultResolver; 28 | 29 | @interface BFAppLinkNavigation () 30 | 31 | @property (nonatomic, copy, readwrite) NSDictionary *extras; 32 | @property (nonatomic, copy, readwrite) NSDictionary *appLinkData; 33 | @property (nonatomic, strong, readwrite) BFAppLink *appLink; 34 | 35 | @end 36 | 37 | @implementation BFAppLinkNavigation 38 | 39 | + (instancetype)navigationWithAppLink:(BFAppLink *)appLink 40 | extras:(NSDictionary *)extras 41 | appLinkData:(NSDictionary *)appLinkData { 42 | BFAppLinkNavigation *navigation = [[self alloc] init]; 43 | navigation.appLink = appLink; 44 | navigation.extras = extras; 45 | navigation.appLinkData = appLinkData; 46 | return navigation; 47 | } 48 | 49 | + (NSDictionary *)callbackAppLinkDataForAppWithName:(NSString *)appName url:(NSString *)url { 50 | return @{BFAppLinkRefererAppLink: @{BFAppLinkRefererAppName: appName, BFAppLinkRefererUrl: url}}; 51 | } 52 | 53 | - (NSString *)stringByEscapingQueryString:(NSString *)string { 54 | #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0 || __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_9 55 | return [string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; 56 | #else 57 | return (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL, 58 | (CFStringRef)string, 59 | NULL, 60 | (CFStringRef) @":/?#[]@!$&'()*+,;=", 61 | kCFStringEncodingUTF8)); 62 | #endif 63 | } 64 | 65 | - (NSURL *)appLinkURLWithTargetURL:(NSURL *)targetUrl error:(NSError **)error { 66 | NSMutableDictionary *appLinkData = [NSMutableDictionary dictionaryWithDictionary:self.appLinkData ?: @{}]; 67 | 68 | // Add applink protocol data 69 | if (!appLinkData[BFAppLinkUserAgentKeyName]) { 70 | appLinkData[BFAppLinkUserAgentKeyName] = [NSString stringWithFormat:@"Bolts iOS %@", BoltsFrameworkVersionString]; 71 | } 72 | if (!appLinkData[BFAppLinkVersionKeyName]) { 73 | appLinkData[BFAppLinkVersionKeyName] = BFAppLinkVersion; 74 | } 75 | appLinkData[BFAppLinkTargetKeyName] = [self.appLink.sourceURL absoluteString]; 76 | appLinkData[BFAppLinkExtrasKeyName] = self.extras ?: @{}; 77 | 78 | // JSON-ify the applink data 79 | NSError *jsonError = nil; 80 | NSData *jsonBlob = [NSJSONSerialization dataWithJSONObject:appLinkData options:0 error:&jsonError]; 81 | if (!jsonError) { 82 | NSString *jsonString = [[NSString alloc] initWithData:jsonBlob encoding:NSUTF8StringEncoding]; 83 | NSString *encoded = [self stringByEscapingQueryString:jsonString]; 84 | 85 | NSString *endUrlString = [NSString stringWithFormat:@"%@%@%@=%@", 86 | [targetUrl absoluteString], 87 | targetUrl.query ? @"&" : @"?", 88 | BFAppLinkDataParameterName, 89 | encoded]; 90 | 91 | return [NSURL URLWithString:endUrlString]; 92 | } else { 93 | if (error) { 94 | *error = jsonError; 95 | } 96 | 97 | // If there was an error encoding the app link data, fail hard. 98 | return nil; 99 | } 100 | } 101 | 102 | - (BFAppLinkNavigationType)navigate:(NSError **)error { 103 | NSURL *openedURL = nil; 104 | NSError *encodingError = nil; 105 | BFAppLinkNavigationType retType = BFAppLinkNavigationTypeFailure; 106 | 107 | // Find the first eligible/launchable target in the BFAppLink. 108 | for (BFAppLinkTarget *target in self.appLink.targets) { 109 | NSURL *appLinkAppURL = [self appLinkURLWithTargetURL:target.URL error:&encodingError]; 110 | if (encodingError || !appLinkAppURL) { 111 | if (error) { 112 | *error = encodingError; 113 | } 114 | } else if ([[UIApplication sharedApplication] openURL:appLinkAppURL]) { 115 | retType = BFAppLinkNavigationTypeApp; 116 | openedURL = appLinkAppURL; 117 | break; 118 | } 119 | } 120 | 121 | if (!openedURL && self.appLink.webURL) { 122 | // Fall back to opening the url in the browser if available. 123 | NSURL *appLinkBrowserURL = [self appLinkURLWithTargetURL:self.appLink.webURL error:&encodingError]; 124 | if (encodingError || !appLinkBrowserURL) { 125 | // If there was an error encoding the app link data, fail hard. 126 | if (error) { 127 | *error = encodingError; 128 | } 129 | } else if ([[UIApplication sharedApplication] openURL:appLinkBrowserURL]) { 130 | // This was a browser navigation. 131 | retType = BFAppLinkNavigationTypeBrowser; 132 | openedURL = appLinkBrowserURL; 133 | } 134 | } 135 | 136 | [self postAppLinkNavigateEventNotificationWithTargetURL:openedURL 137 | error:error ? *error : nil 138 | type:retType]; 139 | return retType; 140 | } 141 | 142 | - (void)postAppLinkNavigateEventNotificationWithTargetURL:(NSURL *)outputURL error:(NSError *)error type:(BFAppLinkNavigationType)type { 143 | NSString *const EVENT_YES_VAL = @"1"; 144 | NSString *const EVENT_NO_VAL = @"0"; 145 | NSMutableDictionary *logData = [[NSMutableDictionary alloc] init]; 146 | 147 | NSString *outputURLScheme = [outputURL scheme]; 148 | NSString *outputURLString = [outputURL absoluteString]; 149 | if (outputURLScheme) { 150 | logData[@"outputURLScheme"] = outputURLScheme; 151 | } 152 | if (outputURLString) { 153 | logData[@"outputURL"] = outputURLString; 154 | } 155 | 156 | NSString *sourceURLString = [self.appLink.sourceURL absoluteString]; 157 | NSString *sourceURLHost = [self.appLink.sourceURL host]; 158 | NSString *sourceURLScheme = [self.appLink.sourceURL scheme]; 159 | if (sourceURLString) { 160 | logData[@"sourceURL"] = sourceURLString; 161 | } 162 | if (sourceURLHost) { 163 | logData[@"sourceHost"] = sourceURLHost; 164 | } 165 | if (sourceURLScheme) { 166 | logData[@"sourceScheme"] = sourceURLScheme; 167 | } 168 | if ([error localizedDescription]) { 169 | logData[@"error"] = [error localizedDescription]; 170 | } 171 | NSString *success = nil; //no 172 | NSString *linkType = nil; // unknown; 173 | switch (type) { 174 | case BFAppLinkNavigationTypeFailure: 175 | success = EVENT_NO_VAL; 176 | linkType = @"fail"; 177 | break; 178 | case BFAppLinkNavigationTypeBrowser: 179 | success = EVENT_YES_VAL; 180 | linkType = @"web"; 181 | break; 182 | case BFAppLinkNavigationTypeApp: 183 | success = EVENT_YES_VAL; 184 | linkType = @"app"; 185 | break; 186 | default: 187 | break; 188 | } 189 | if (success) { 190 | logData[@"success"] = success; 191 | } 192 | if (linkType) { 193 | logData[@"type"] = linkType; 194 | } 195 | 196 | if ([self.appLink isBackToReferrer]) { 197 | [BFMeasurementEvent postNotificationForEventName:BFAppLinkNavigateBackToReferrerEventName args:logData]; 198 | } else { 199 | [BFMeasurementEvent postNotificationForEventName:BFAppLinkNavigateOutEventName args:logData]; 200 | } 201 | } 202 | 203 | + (BFTask *)resolveAppLinkInBackground:(NSURL *)destination resolver:(id)resolver { 204 | return [resolver appLinkFromURLInBackground:destination]; 205 | } 206 | 207 | + (BFTask *)resolveAppLinkInBackground:(NSURL *)destination { 208 | return [self resolveAppLinkInBackground:destination resolver:[self defaultResolver]]; 209 | } 210 | 211 | + (BFTask *)navigateToURLInBackground:(NSURL *)destination { 212 | return [self navigateToURLInBackground:destination 213 | resolver:[self defaultResolver]]; 214 | } 215 | 216 | + (BFTask *)navigateToURLInBackground:(NSURL *)destination 217 | resolver:(id)resolver { 218 | BFTask *resolutionTask = [self resolveAppLinkInBackground:destination 219 | resolver:resolver]; 220 | return [resolutionTask continueWithExecutor:[BFExecutor mainThreadExecutor] 221 | withSuccessBlock:^id(BFTask *task) { 222 | NSError *error = nil; 223 | BFAppLinkNavigationType result = [self navigateToAppLink:task.result 224 | error:&error]; 225 | if (error) { 226 | return [BFTask taskWithError:error]; 227 | } else { 228 | return @(result); 229 | } 230 | }]; 231 | } 232 | 233 | + (BFAppLinkNavigationType)navigateToAppLink:(BFAppLink *)link error:(NSError **)error { 234 | return [[BFAppLinkNavigation navigationWithAppLink:link 235 | extras:nil 236 | appLinkData:nil] navigate:error]; 237 | } 238 | 239 | + (BFAppLinkNavigationType)navigationTypeForLink:(BFAppLink *)link { 240 | return [[self navigationWithAppLink:link extras:nil appLinkData:nil] navigationType]; 241 | } 242 | 243 | - (BFAppLinkNavigationType)navigationType { 244 | BFAppLinkTarget *eligibleTarget = nil; 245 | for (BFAppLinkTarget *target in self.appLink.targets) { 246 | if ([[UIApplication sharedApplication] canOpenURL:target.URL]) { 247 | eligibleTarget = target; 248 | break; 249 | } 250 | } 251 | 252 | if (eligibleTarget != nil) { 253 | NSURL *appLinkURL = [self appLinkURLWithTargetURL:eligibleTarget.URL error:nil]; 254 | if (appLinkURL != nil) { 255 | return BFAppLinkNavigationTypeApp; 256 | } else { 257 | return BFAppLinkNavigationTypeFailure; 258 | } 259 | } 260 | 261 | if (self.appLink.webURL != nil) { 262 | NSURL *appLinkURL = [self appLinkURLWithTargetURL:eligibleTarget.URL error:nil]; 263 | if (appLinkURL != nil) { 264 | return BFAppLinkNavigationTypeBrowser; 265 | } else { 266 | return BFAppLinkNavigationTypeFailure; 267 | } 268 | } 269 | 270 | return BFAppLinkNavigationTypeFailure; 271 | } 272 | 273 | + (id)defaultResolver { 274 | if (defaultResolver) { 275 | return defaultResolver; 276 | } 277 | return [BFWebViewAppLinkResolver sharedInstance]; 278 | } 279 | 280 | + (void)setDefaultResolver:(id)resolver { 281 | defaultResolver = resolver; 282 | } 283 | 284 | @end 285 | -------------------------------------------------------------------------------- /Bolts/iOS/BFAppLinkResolving.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | 13 | @class BFTask; 14 | 15 | /*! 16 | Implement this protocol to provide an alternate strategy for resolving 17 | App Links that may include pre-fetching, caching, or querying for App Link 18 | data from an index provided by a service provider. 19 | */ 20 | @protocol BFAppLinkResolving 21 | 22 | /*! 23 | Asynchronously resolves App Link data for a given URL. 24 | 25 | @param url The URL to resolve into an App Link. 26 | @returns A BFTask that will return a BFAppLink for the given URL. 27 | */ 28 | - (BFTask *)appLinkFromURLInBackground:(NSURL *)url NS_EXTENSION_UNAVAILABLE_IOS("Not available in app extension"); 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /Bolts/iOS/BFAppLinkReturnToRefererController.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | #import 13 | 14 | #import 15 | 16 | @class BFAppLink; 17 | @class BFAppLinkReturnToRefererController; 18 | 19 | /*! 20 | Protocol that a class can implement in order to be notified when the user has navigated back 21 | to the referer of an App Link. 22 | */ 23 | @protocol BFAppLinkReturnToRefererControllerDelegate 24 | 25 | @optional 26 | 27 | /*! Called when the user has tapped to navigate, but before the navigation has been performed. */ 28 | - (void)returnToRefererController:(BFAppLinkReturnToRefererController *)controller 29 | willNavigateToAppLink:(BFAppLink *)appLink; 30 | 31 | /*! Called after the navigation has been attempted, with an indication of whether the referer 32 | app link was successfully opened. */ 33 | - (void)returnToRefererController:(BFAppLinkReturnToRefererController *)controller 34 | didNavigateToAppLink:(BFAppLink *)url 35 | type:(BFAppLinkNavigationType)type; 36 | 37 | @end 38 | 39 | /*! 40 | A controller class that implements default behavior for a BFAppLinkReturnToRefererView, including 41 | the ability to display the view above the navigation bar for navigation-based apps. 42 | */ 43 | NS_EXTENSION_UNAVAILABLE_IOS("Not available in app extension") 44 | @interface BFAppLinkReturnToRefererController : NSObject 45 | 46 | /*! 47 | The delegate that will be notified when the user navigates back to the referer. 48 | */ 49 | @property (nonatomic, weak) id delegate; 50 | 51 | /*! 52 | The BFAppLinkReturnToRefererView this controller is controlling. 53 | */ 54 | @property (nonatomic, strong) BFAppLinkReturnToRefererView *view; 55 | 56 | /*! 57 | Initializes a controller suitable for controlling a BFAppLinkReturnToRefererView that is to be displayed 58 | contained within another UIView (i.e., not displayed above the navigation bar). 59 | */ 60 | - (instancetype)init; 61 | 62 | /*! 63 | Initializes a controller suitable for controlling a BFAppLinkReturnToRefererView that is to be displayed 64 | displayed above the navigation bar. 65 | */ 66 | - (instancetype)initForDisplayAboveNavController:(UINavigationController *)navController; 67 | 68 | /*! 69 | Removes the view entirely from the navigation controller it is currently displayed in. 70 | */ 71 | - (void)removeFromNavController; 72 | 73 | /*! 74 | Shows the BFAppLinkReturnToRefererView with the specified referer information. If nil or missing data, 75 | the view will not be displayed. */ 76 | - (void)showViewForRefererAppLink:(BFAppLink *)refererAppLink; 77 | 78 | /*! 79 | Shows the BFAppLinkReturnToRefererView with referer information extracted from the specified URL. 80 | If nil or missing referer App Link data, the view will not be displayed. */ 81 | - (void)showViewForRefererURL:(NSURL *)url; 82 | 83 | /*! 84 | Closes the view, possibly animating it. 85 | */ 86 | - (void)closeViewAnimated:(BOOL)animated; 87 | 88 | @end 89 | -------------------------------------------------------------------------------- /Bolts/iOS/BFAppLinkReturnToRefererController.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import "BFAppLinkReturnToRefererController.h" 12 | 13 | #import "BFAppLink.h" 14 | #import "BFAppLinkReturnToRefererView_Internal.h" 15 | #import "BFURL_Internal.h" 16 | 17 | static const CFTimeInterval kBFViewAnimationDuration = 0.25f; 18 | 19 | @implementation BFAppLinkReturnToRefererController { 20 | UINavigationController *_navigationController; 21 | BFAppLinkReturnToRefererView *_view; 22 | } 23 | 24 | #pragma mark - Object lifecycle 25 | 26 | - (instancetype)init { 27 | return [self initForDisplayAboveNavController:nil]; 28 | } 29 | 30 | - (instancetype)initForDisplayAboveNavController:(UINavigationController *)navController { 31 | self = [super init]; 32 | if (self) { 33 | _navigationController = navController; 34 | 35 | if (_navigationController != nil) { 36 | NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 37 | [nc addObserver:self 38 | selector:@selector(statusBarFrameWillChange:) 39 | name:UIApplicationWillChangeStatusBarFrameNotification 40 | object:nil]; 41 | [nc addObserver:self 42 | selector:@selector(statusBarFrameDidChange:) 43 | name:UIApplicationDidChangeStatusBarFrameNotification 44 | object:nil]; 45 | [nc addObserver:self 46 | selector:@selector(orientationDidChange:) 47 | name:UIDeviceOrientationDidChangeNotification 48 | object:nil]; 49 | } 50 | } 51 | return self; 52 | } 53 | 54 | - (void)dealloc { 55 | _view.delegate = nil; 56 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 57 | } 58 | 59 | #pragma mark - Public API 60 | 61 | - (BFAppLinkReturnToRefererView *)view { 62 | if (!_view) { 63 | self.view = [[BFAppLinkReturnToRefererView alloc] initWithFrame:CGRectZero]; 64 | if (_navigationController) { 65 | [_navigationController.view addSubview:_view]; 66 | } 67 | } 68 | return _view; 69 | } 70 | 71 | - (void)setView:(BFAppLinkReturnToRefererView *)view { 72 | if (_view != view) { 73 | _view.delegate = nil; 74 | } 75 | 76 | _view = view; 77 | _view.delegate = self; 78 | 79 | if (_navigationController) { 80 | _view.includeStatusBarInSize = BFIncludeStatusBarInSizeAlways; 81 | } 82 | } 83 | 84 | - (void)showViewForRefererAppLink:(BFAppLink *)refererAppLink { 85 | self.view.refererAppLink = refererAppLink; 86 | 87 | [_view sizeToFit]; 88 | 89 | if (_navigationController) { 90 | if (!_view.closed) { 91 | dispatch_async(dispatch_get_main_queue(), ^{ 92 | [self moveNavigationBar]; 93 | }); 94 | } 95 | } 96 | } 97 | 98 | - (void)showViewForRefererURL:(NSURL *)url { 99 | BFAppLink *appLink = [BFURL URLForRenderBackToReferrerBarURL:url].appLinkReferer; 100 | [self showViewForRefererAppLink:appLink]; 101 | } 102 | 103 | - (void)removeFromNavController { 104 | if (_navigationController) { 105 | [_view removeFromSuperview]; 106 | _navigationController = nil; 107 | } 108 | } 109 | 110 | #pragma mark - BFAppLinkReturnToRefererViewDelegate 111 | 112 | - (void)returnToRefererViewDidTapInsideCloseButton:(BFAppLinkReturnToRefererView *)view { 113 | [self closeViewAnimated:YES explicitlyClosed:YES]; 114 | } 115 | 116 | - (void)returnToRefererViewDidTapInsideLink:(BFAppLinkReturnToRefererView *)view 117 | link:(BFAppLink *)link { 118 | [self openRefererAppLink:link]; 119 | [self closeViewAnimated:NO explicitlyClosed:NO]; 120 | } 121 | 122 | #pragma mark - Private 123 | 124 | - (void)statusBarFrameWillChange:(NSNotification *)notification { 125 | NSValue *rectValue = [[notification userInfo] valueForKey:UIApplicationStatusBarFrameUserInfoKey]; 126 | CGRect newFrame; 127 | [rectValue getValue:&newFrame]; 128 | 129 | if (_navigationController && !_view.closed) { 130 | if (CGRectGetHeight(newFrame) == 40) { 131 | UIViewAnimationOptions options = UIViewAnimationOptionBeginFromCurrentState; 132 | [UIView animateWithDuration:kBFViewAnimationDuration delay:0.0 options:options animations:^{ 133 | self->_view.frame = CGRectMake(0.0, 0.0, CGRectGetWidth(self->_view.bounds), 0.0); 134 | } completion:nil]; 135 | } 136 | } 137 | } 138 | 139 | - (void)statusBarFrameDidChange:(NSNotification *)notification { 140 | NSValue *rectValue = [[notification userInfo] valueForKey:UIApplicationStatusBarFrameUserInfoKey]; 141 | CGRect newFrame; 142 | [rectValue getValue:&newFrame]; 143 | 144 | if (_navigationController && !_view.closed) { 145 | if (CGRectGetHeight(newFrame) == 40) { 146 | UIViewAnimationOptions options = UIViewAnimationOptionBeginFromCurrentState; 147 | [UIView animateWithDuration:kBFViewAnimationDuration delay:0.0 options:options animations:^{ 148 | [self->_view sizeToFit]; 149 | [self moveNavigationBar]; 150 | } completion:nil]; 151 | } 152 | } 153 | } 154 | 155 | - (void)orientationDidChange:(NSNotificationCenter *)notification { 156 | if (_navigationController && !_view.closed && CGRectGetHeight(_view.bounds) > 0) { 157 | dispatch_async(dispatch_get_main_queue(), ^{ 158 | [self moveNavigationBar]; 159 | }); 160 | } 161 | } 162 | 163 | - (void)moveNavigationBar { 164 | if (_view.closed || !_view.refererAppLink) { 165 | return; 166 | } 167 | 168 | [self updateNavigationBarY:CGRectGetHeight(_view.bounds)]; 169 | } 170 | 171 | - (void)updateNavigationBarY:(CGFloat)y { 172 | UINavigationBar *navigationBar = _navigationController.navigationBar; 173 | CGRect navigationBarFrame = navigationBar.frame; 174 | CGFloat oldContainerViewY = CGRectGetMaxY(navigationBarFrame); 175 | navigationBarFrame.origin.y = y; 176 | navigationBar.frame = navigationBarFrame; 177 | 178 | CGFloat dy = CGRectGetMaxY(navigationBarFrame) - oldContainerViewY; 179 | UIView *containerView = _navigationController.visibleViewController.view.superview; 180 | containerView.frame = UIEdgeInsetsInsetRect(containerView.frame, UIEdgeInsetsMake(dy, 0.0, 0.0, 0.0)); 181 | } 182 | 183 | - (void)closeViewAnimated:(BOOL)animated { 184 | [self closeViewAnimated:animated explicitlyClosed:YES]; 185 | } 186 | 187 | - (void)closeViewAnimated:(BOOL)animated explicitlyClosed:(BOOL)explicitlyClosed { 188 | void (^closer)(void) = ^{ 189 | if (self->_navigationController) { 190 | [self updateNavigationBarY:self->_view.statusBarHeight]; 191 | } 192 | 193 | CGRect frame = self->_view.frame; 194 | frame.size.height = 0.0; 195 | self->_view.frame = frame; 196 | }; 197 | 198 | if (animated) { 199 | [UIView animateWithDuration:kBFViewAnimationDuration animations:^{ 200 | closer(); 201 | } completion:^(BOOL finished) { 202 | if (explicitlyClosed) { 203 | self->_view.closed = YES; 204 | } 205 | }]; 206 | } else { 207 | closer(); 208 | if (explicitlyClosed) { 209 | self->_view.closed = YES; 210 | } 211 | } 212 | } 213 | 214 | - (void)openRefererAppLink:(BFAppLink *)refererAppLink { 215 | if (refererAppLink) { 216 | id delegate = _delegate; 217 | if ([delegate respondsToSelector:@selector(returnToRefererController:willNavigateToAppLink:)]) { 218 | [delegate returnToRefererController:self willNavigateToAppLink:refererAppLink]; 219 | } 220 | 221 | NSError *error = nil; 222 | BFAppLinkNavigationType type = [BFAppLinkNavigation navigateToAppLink:refererAppLink error:&error]; 223 | 224 | if ([delegate respondsToSelector:@selector(returnToRefererController:didNavigateToAppLink:type:)]) { 225 | [delegate returnToRefererController:self didNavigateToAppLink:refererAppLink type:type]; 226 | } 227 | } 228 | } 229 | 230 | @end 231 | -------------------------------------------------------------------------------- /Bolts/iOS/BFAppLinkReturnToRefererView.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | #import 13 | 14 | #import 15 | 16 | @class BFAppLinkReturnToRefererView; 17 | @class BFURL; 18 | 19 | typedef NS_ENUM(NSUInteger, BFIncludeStatusBarInSize) { 20 | BFIncludeStatusBarInSizeNever, 21 | BFIncludeStatusBarInSizeIOS7AndLater, 22 | BFIncludeStatusBarInSizeAlways, 23 | }; 24 | 25 | /*! 26 | Protocol that a class can implement in order to be notified when the user has navigated back 27 | to the referer of an App Link. 28 | */ 29 | @protocol BFAppLinkReturnToRefererViewDelegate 30 | 31 | /*! 32 | Called when the user has tapped inside the close button. 33 | */ 34 | - (void)returnToRefererViewDidTapInsideCloseButton:(BFAppLinkReturnToRefererView *)view; 35 | 36 | /*! 37 | Called when the user has tapped inside the App Link portion of the view. 38 | */ 39 | - (void)returnToRefererViewDidTapInsideLink:(BFAppLinkReturnToRefererView *)view 40 | link:(BFAppLink *)link; 41 | 42 | @end 43 | 44 | /*! 45 | Provides a UIView that displays a button allowing users to navigate back to the 46 | application that launched the App Link currently being handled, if the App Link 47 | contained referer data. The user can also close the view by clicking a close button 48 | rather than navigating away. If the view is provided an App Link that does not contain 49 | referer data, it will have zero size and no UI will be displayed. 50 | */ 51 | NS_EXTENSION_UNAVAILABLE_IOS("Not available in app extension") 52 | @interface BFAppLinkReturnToRefererView : UIView 53 | 54 | /*! 55 | The delegate that will be notified when the user navigates back to the referer. 56 | */ 57 | @property (nonatomic, weak) id delegate; 58 | 59 | /*! 60 | The color of the text label and close button. 61 | */ 62 | @property (nonatomic, strong) UIColor *textColor; 63 | 64 | @property (nonatomic, strong) BFAppLink *refererAppLink; 65 | 66 | /*! 67 | Indicates whether to extend the size of the view to include the current status bar 68 | size, for use in scenarios where the view might extend under the status bar on iOS 7 and 69 | above; this property has no effect on earlier versions of iOS. 70 | */ 71 | @property (nonatomic, assign) BFIncludeStatusBarInSize includeStatusBarInSize; 72 | 73 | /*! 74 | Indicates whether the user has closed the view by clicking the close button. 75 | */ 76 | @property (nonatomic, assign) BOOL closed; 77 | 78 | @end 79 | -------------------------------------------------------------------------------- /Bolts/iOS/BFAppLinkReturnToRefererView.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import "BFAppLinkReturnToRefererView.h" 12 | 13 | #import "BFAppLink.h" 14 | #import "BFAppLinkTarget.h" 15 | 16 | static const CGFloat BFMarginX = 8.5f; 17 | static const CGFloat BFMarginY = 8.5f; 18 | 19 | static NSString *const BFRefererAppLink = @"referer_app_link"; 20 | static NSString *const BFRefererAppName = @"app_name"; 21 | static NSString *const BFRefererUrl = @"url"; 22 | static const CGFloat BFCloseButtonWidth = 12.0; 23 | static const CGFloat BFCloseButtonHeight = 12.0; 24 | 25 | @interface BFAppLinkReturnToRefererView () 26 | 27 | @property (nonatomic, strong) UILabel *labelView; 28 | @property (nonatomic, strong) UIButton *closeButton; 29 | @property (nonatomic, strong) UITapGestureRecognizer *insideTapGestureRecognizer; 30 | 31 | @end 32 | 33 | @implementation BFAppLinkReturnToRefererView { 34 | BOOL _explicitlyHidden; 35 | } 36 | 37 | #pragma mark - Initialization 38 | 39 | - (instancetype)initWithFrame:(CGRect)frame { 40 | self = [super initWithFrame:frame]; 41 | if (self) { 42 | [self commonInit]; 43 | [self sizeToFit]; 44 | } 45 | return self; 46 | } 47 | 48 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 49 | self = [super initWithCoder:aDecoder]; 50 | if (self) { 51 | [self commonInit]; 52 | } 53 | return self; 54 | } 55 | 56 | - (void)commonInit { 57 | // Initialization code 58 | _includeStatusBarInSize = BFIncludeStatusBarInSizeIOS7AndLater; 59 | 60 | // iOS 7 system blue color 61 | self.backgroundColor = [UIColor colorWithRed:0.0f green:122.0f / 255.0f blue:1.0f alpha:1.0f]; 62 | self.textColor = [UIColor whiteColor]; 63 | self.clipsToBounds = YES; 64 | 65 | [self initViews]; 66 | } 67 | 68 | - (void)initViews { 69 | if (!_labelView && !_closeButton) { 70 | _closeButton = [UIButton buttonWithType:UIButtonTypeCustom]; 71 | _closeButton.backgroundColor = [UIColor clearColor]; 72 | _closeButton.userInteractionEnabled = YES; 73 | _closeButton.clipsToBounds = YES; 74 | _closeButton.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin; 75 | _closeButton.contentMode = UIViewContentModeCenter; 76 | [_closeButton addTarget:self action:@selector(closeButtonTapped:) forControlEvents:UIControlEventTouchUpInside]; 77 | 78 | [self addSubview:_closeButton]; 79 | 80 | _labelView = [[UILabel alloc] initWithFrame:CGRectZero]; 81 | _labelView.font = [UIFont systemFontOfSize:[UIFont smallSystemFontSize]]; 82 | _labelView.textColor = [UIColor whiteColor]; 83 | _labelView.backgroundColor = [UIColor clearColor]; 84 | #ifdef __IPHONE_6_0 85 | _labelView.textAlignment = NSTextAlignmentCenter; 86 | #else 87 | _labelView.textAlignment = UITextAlignmentCenter; 88 | #endif 89 | _labelView.clipsToBounds = YES; 90 | [self updateLabelText]; 91 | [self addSubview:_labelView]; 92 | 93 | _insideTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTapInside:)]; 94 | _labelView.userInteractionEnabled = YES; 95 | [_labelView addGestureRecognizer:_insideTapGestureRecognizer]; 96 | 97 | [self updateColors]; 98 | } 99 | } 100 | 101 | #pragma mark - Layout 102 | 103 | - (CGSize)intrinsicContentSize { 104 | CGSize size = self.bounds.size; 105 | if (_closed || !self.hasRefererData) { 106 | size.height = 0.0; 107 | } else { 108 | CGSize labelSize = [_labelView sizeThatFits:size]; 109 | size = CGSizeMake(size.width, labelSize.height + 2 * BFMarginY + self.statusBarHeight); 110 | } 111 | return size; 112 | } 113 | 114 | - (void)layoutSubviews { 115 | [super layoutSubviews]; 116 | 117 | CGRect bounds = self.bounds; 118 | 119 | _labelView.preferredMaxLayoutWidth = _labelView.bounds.size.width; 120 | CGSize labelSize = [_labelView sizeThatFits:bounds.size]; 121 | _labelView.frame = CGRectMake(BFMarginX, 122 | CGRectGetMaxY(bounds) - labelSize.height - 1.5f * BFMarginY, 123 | CGRectGetMaxX(bounds) - BFCloseButtonWidth - 3 * BFMarginX, 124 | labelSize.height + BFMarginY); 125 | 126 | _closeButton.frame = CGRectMake(CGRectGetMaxX(bounds) - BFCloseButtonWidth - 2 * BFMarginX, 127 | _labelView.center.y - BFCloseButtonHeight / 2.0f - BFMarginY, 128 | BFCloseButtonWidth + 2 * BFMarginX, 129 | BFCloseButtonHeight + 2 * BFMarginY); 130 | } 131 | 132 | - (CGSize)sizeThatFits:(CGSize)size { 133 | if (_closed || !self.hasRefererData) { 134 | size = CGSizeMake(size.width, 0.0); 135 | } else { 136 | CGSize labelSize = [_labelView sizeThatFits:size]; 137 | size = CGSizeMake(size.width, labelSize.height + 2 * BFMarginY + self.statusBarHeight); 138 | } 139 | return size; 140 | } 141 | 142 | - (CGFloat)statusBarHeight { 143 | UIApplication *application = [UIApplication sharedApplication]; 144 | 145 | BOOL include; 146 | switch (_includeStatusBarInSize) { 147 | case BFIncludeStatusBarInSizeAlways: 148 | include = YES; 149 | break; 150 | case BFIncludeStatusBarInSizeIOS7AndLater: { 151 | float systemVersion = [[[UIDevice currentDevice] systemVersion] floatValue]; 152 | include = (systemVersion >= 7.0); 153 | break; 154 | } 155 | case BFIncludeStatusBarInSizeNever: 156 | include = NO; 157 | break; 158 | } 159 | if (include && !application.statusBarHidden) { 160 | BOOL landscape = UIInterfaceOrientationIsLandscape(application.statusBarOrientation); 161 | CGRect statusBarFrame = application.statusBarFrame; 162 | return landscape ? CGRectGetWidth(statusBarFrame) : CGRectGetHeight(statusBarFrame); 163 | } 164 | 165 | return 0; 166 | } 167 | 168 | #pragma mark - Public API 169 | 170 | - (void)setIncludeStatusBarInSize:(BFIncludeStatusBarInSize)includeStatusBarInSize { 171 | _includeStatusBarInSize = includeStatusBarInSize; 172 | [self setNeedsLayout]; 173 | [self invalidateIntrinsicContentSize]; 174 | } 175 | 176 | - (void)setTextColor:(UIColor *)textColor { 177 | _textColor = textColor; 178 | [self updateColors]; 179 | } 180 | 181 | - (void)setRefererAppLink:(BFAppLink *)refererAppLink { 182 | _refererAppLink = refererAppLink; 183 | [self updateLabelText]; 184 | [self updateHidden]; 185 | [self invalidateIntrinsicContentSize]; 186 | } 187 | 188 | - (void)setClosed:(BOOL)closed { 189 | if (_closed != closed) { 190 | _closed = closed; 191 | [self updateHidden]; 192 | [self invalidateIntrinsicContentSize]; 193 | } 194 | } 195 | 196 | - (void)setHidden:(BOOL)hidden { 197 | _explicitlyHidden = hidden; 198 | [self updateHidden]; 199 | } 200 | 201 | #pragma mark - Private 202 | 203 | - (void)updateLabelText { 204 | NSString *appName = (_refererAppLink && _refererAppLink.targets[0]) ? [_refererAppLink.targets[0] appName] : nil; 205 | _labelView.text = [self localizedLabelForReferer:appName]; 206 | } 207 | 208 | - (void)updateColors { 209 | UIImage *closeButtonImage = [self drawCloseButtonImageWithColor:_textColor]; 210 | 211 | _labelView.textColor = _textColor; 212 | [_closeButton setImage:closeButtonImage forState:UIControlStateNormal]; 213 | } 214 | 215 | - (UIImage *)drawCloseButtonImageWithColor:(UIColor *)color { 216 | 217 | UIGraphicsBeginImageContextWithOptions(CGSizeMake(BFCloseButtonWidth, BFCloseButtonHeight), NO, 0.0f); 218 | 219 | CGContextRef context = UIGraphicsGetCurrentContext(); 220 | 221 | CGContextSetStrokeColorWithColor(context, [color CGColor]); 222 | CGContextSetFillColorWithColor(context, [color CGColor]); 223 | 224 | CGContextSetLineWidth(context, 1.25f); 225 | 226 | CGFloat inset = 0.5f; 227 | 228 | CGContextMoveToPoint(context, inset, inset); 229 | CGContextAddLineToPoint(context, BFCloseButtonWidth - inset, BFCloseButtonHeight - inset); 230 | CGContextStrokePath(context); 231 | 232 | CGContextMoveToPoint(context, BFCloseButtonWidth - inset, inset); 233 | CGContextAddLineToPoint(context, inset, BFCloseButtonHeight - inset); 234 | CGContextStrokePath(context); 235 | 236 | UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); 237 | UIGraphicsEndImageContext(); 238 | 239 | return result; 240 | } 241 | 242 | - (NSString *)localizedLabelForReferer:(NSString *)refererName { 243 | if (!refererName) { 244 | return nil; 245 | } 246 | 247 | NSString *format = NSLocalizedString(@"Touch to return to %1$@", @"Format for the string to return to a calling app."); 248 | 249 | return [NSString stringWithFormat:format, refererName]; 250 | } 251 | 252 | - (BOOL)hasRefererData { 253 | return _refererAppLink && _refererAppLink.targets[0]; 254 | } 255 | 256 | - (void)closeButtonTapped:(id)sender { 257 | [_delegate returnToRefererViewDidTapInsideCloseButton:self]; 258 | } 259 | 260 | - (void)onTapInside:(UIGestureRecognizer *)sender { 261 | [_delegate returnToRefererViewDidTapInsideLink:self link:_refererAppLink]; 262 | } 263 | 264 | - (void)updateHidden { 265 | [super setHidden:_explicitlyHidden || _closed || !self.hasRefererData]; 266 | } 267 | 268 | @end 269 | -------------------------------------------------------------------------------- /Bolts/iOS/BFAppLinkTarget.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | 13 | /*! 14 | Represents a target defined in App Link metadata, consisting of at least 15 | a URL, and optionally an App Store ID and name. 16 | */ 17 | @interface BFAppLinkTarget : NSObject 18 | 19 | /*! Creates a BFAppLinkTarget with the given app site and target URL. */ 20 | + (instancetype)appLinkTargetWithURL:(NSURL *)url 21 | appStoreId:(NSString *)appStoreId 22 | appName:(NSString *)appName; 23 | 24 | /*! The URL prefix for this app link target */ 25 | @property (nonatomic, strong, readonly) NSURL *URL; 26 | 27 | /*! The app ID for the app store */ 28 | @property (nonatomic, copy, readonly) NSString *appStoreId; 29 | 30 | /*! The name of the app */ 31 | @property (nonatomic, copy, readonly) NSString *appName; 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /Bolts/iOS/BFAppLinkTarget.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import "BFAppLinkTarget.h" 12 | 13 | @interface BFAppLinkTarget () 14 | 15 | @property (nonatomic, strong, readwrite) NSURL *URL; 16 | @property (nonatomic, copy, readwrite) NSString *appStoreId; 17 | @property (nonatomic, copy, readwrite) NSString *appName; 18 | 19 | @end 20 | 21 | @implementation BFAppLinkTarget 22 | 23 | + (instancetype)appLinkTargetWithURL:(NSURL *)url 24 | appStoreId:(NSString *)appStoreId 25 | appName:(NSString *)appName { 26 | BFAppLinkTarget *target = [[self alloc] init]; 27 | target.URL = url; 28 | target.appStoreId = appStoreId; 29 | target.appName = appName; 30 | return target; 31 | } 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /Bolts/iOS/BFMeasurementEvent.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | 13 | /*! The name of the notification posted by BFMeasurementEvent */ 14 | FOUNDATION_EXPORT NSString *const BFMeasurementEventNotificationName; 15 | 16 | /*! Defines keys in the userInfo object for the notification named BFMeasurementEventNotificationName */ 17 | /*! The string field for the name of the event */ 18 | FOUNDATION_EXPORT NSString *const BFMeasurementEventNameKey; 19 | /*! The dictionary field for the arguments of the event */ 20 | FOUNDATION_EXPORT NSString *const BFMeasurementEventArgsKey; 21 | 22 | /*! Bolts Events raised by BFMeasurementEvent for Applink */ 23 | /*! 24 | The name of the event posted when [BFURL URLWithURL:] is called successfully. This represents the successful parsing of an app link URL. 25 | */ 26 | FOUNDATION_EXPORT NSString *const BFAppLinkParseEventName; 27 | 28 | /*! 29 | The name of the event posted when [BFURL URLWithInboundURL:] is called successfully. 30 | This represents parsing an inbound app link URL from a different application 31 | */ 32 | FOUNDATION_EXPORT NSString *const BFAppLinkNavigateInEventName; 33 | 34 | /*! The event raised when the user navigates from your app to other apps */ 35 | FOUNDATION_EXPORT NSString *const BFAppLinkNavigateOutEventName; 36 | 37 | /*! 38 | The event raised when the user navigates out from your app and back to the referrer app. 39 | e.g when the user leaves your app after tapping the back-to-referrer navigation bar 40 | */ 41 | FOUNDATION_EXPORT NSString *const BFAppLinkNavigateBackToReferrerEventName; 42 | 43 | @interface BFMeasurementEvent : NSObject 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /Bolts/iOS/BFMeasurementEvent.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import "BFMeasurementEvent_Internal.h" 12 | 13 | NSString *const BFMeasurementEventNotificationName = @"com.parse.bolts.measurement_event"; 14 | 15 | NSString *const BFMeasurementEventNameKey = @"event_name"; 16 | NSString *const BFMeasurementEventArgsKey = @"event_args"; 17 | 18 | /* app Link Event raised by this BFURL */ 19 | NSString *const BFAppLinkParseEventName = @"al_link_parse"; 20 | NSString *const BFAppLinkNavigateInEventName = @"al_nav_in"; 21 | 22 | /*! AppLink events raised in this class */ 23 | NSString *const BFAppLinkNavigateOutEventName = @"al_nav_out"; 24 | NSString *const BFAppLinkNavigateBackToReferrerEventName = @"al_ref_back_out"; 25 | 26 | __attribute__((noinline)) void warnOnMissingEventName() { 27 | NSLog(@"Warning: Missing event name when logging bolts measurement event. \n" 28 | " Ignoring this event in logging."); 29 | } 30 | 31 | @implementation BFMeasurementEvent { 32 | NSString *_name; 33 | NSDictionary *_args; 34 | } 35 | 36 | - (void)postNotification { 37 | if (!_name) { 38 | warnOnMissingEventName(); 39 | return; 40 | } 41 | NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 42 | NSDictionary *userInfo = @{BFMeasurementEventNameKey : _name, 43 | BFMeasurementEventArgsKey : _args}; 44 | 45 | [center postNotificationName:BFMeasurementEventNotificationName 46 | object:self 47 | userInfo:userInfo]; 48 | } 49 | 50 | - (instancetype)initEventWithName:(NSString *)name args:(NSDictionary *)args { 51 | if ((self = [super init])) { 52 | _name = name; 53 | _args = args ? args : @{}; 54 | } 55 | return self; 56 | } 57 | 58 | + (void)postNotificationForEventName:(NSString *)name args:(NSDictionary *)args { 59 | [[[self alloc] initEventWithName:name args:args] postNotification]; 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /Bolts/iOS/BFURL.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | 13 | @class BFAppLink; 14 | 15 | /*! 16 | Provides a set of utilities for working with NSURLs, such as parsing of query parameters 17 | and handling for App Link requests. 18 | */ 19 | @interface BFURL : NSObject 20 | 21 | /*! 22 | Creates a link target from a raw URL. 23 | On success, this posts the BFAppLinkParseEventName measurement event. If you are constructing the BFURL within your application delegate's 24 | application:openURL:sourceApplication:annotation:, you should instead use URLWithInboundURL:sourceApplication: 25 | to support better BFMeasurementEvent notifications 26 | @param url The instance of `NSURL` to create BFURL from. 27 | */ 28 | + (BFURL *)URLWithURL:(NSURL *)url; 29 | 30 | /*! 31 | Creates a link target from a raw URL received from an external application. This is typically called from the app delegate's 32 | application:openURL:sourceApplication:annotation: and will post the BFAppLinkNavigateInEventName measurement event. 33 | @param url The instance of `NSURL` to create BFURL from. 34 | @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: 35 | */ 36 | + (BFURL *)URLWithInboundURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication; 37 | 38 | /*! 39 | Gets the target URL. If the link is an App Link, this is the target of the App Link. 40 | Otherwise, it is the url that created the target. 41 | */ 42 | @property (nonatomic, strong, readonly) NSURL *targetURL; 43 | 44 | /*! 45 | Gets the query parameters for the target, parsed into an NSDictionary. 46 | */ 47 | @property (nonatomic, strong, readonly) NSDictionary *targetQueryParameters; 48 | 49 | /*! 50 | If this link target is an App Link, this is the data found in al_applink_data. 51 | Otherwise, it is nil. 52 | */ 53 | @property (nonatomic, strong, readonly) NSDictionary *appLinkData; 54 | 55 | /*! 56 | If this link target is an App Link, this is the data found in extras. 57 | */ 58 | @property (nonatomic, strong, readonly) NSDictionary *appLinkExtras; 59 | 60 | /*! 61 | The App Link indicating how to navigate back to the referer app, if any. 62 | */ 63 | @property (nonatomic, strong, readonly) BFAppLink *appLinkReferer; 64 | 65 | /*! 66 | The URL that was used to create this BFURL. 67 | */ 68 | @property (nonatomic, strong, readonly) NSURL *inputURL; 69 | 70 | /*! 71 | The query parameters of the inputURL, parsed into an NSDictionary. 72 | */ 73 | @property (nonatomic, strong, readonly) NSDictionary *inputQueryParameters; 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /Bolts/iOS/BFURL.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import "BFURL_Internal.h" 12 | #import "BFAppLink_Internal.h" 13 | #import "BFAppLinkTarget.h" 14 | #import "BFMeasurementEvent_Internal.h" 15 | 16 | @implementation BFURL 17 | 18 | - (instancetype)initWithURL:(NSURL *)url forOpenInboundURL:(BOOL)forOpenURLEvent sourceApplication:(NSString *)sourceApplication forRenderBackToReferrerBar:(BOOL)forRenderBackToReferrerBar { 19 | self = [super init]; 20 | if (!self) return nil; 21 | 22 | _inputURL = url; 23 | _targetURL = url; 24 | 25 | // Parse the query string parameters for the base URL 26 | NSDictionary *baseQuery = [BFURL queryParametersForURL:url]; 27 | _inputQueryParameters = baseQuery; 28 | _targetQueryParameters = baseQuery; 29 | 30 | // Check for applink_data 31 | NSString *appLinkDataString = baseQuery[BFAppLinkDataParameterName]; 32 | if (appLinkDataString) { 33 | // Try to parse the JSON 34 | NSError *error = nil; 35 | NSDictionary *applinkData = [NSJSONSerialization JSONObjectWithData:[appLinkDataString dataUsingEncoding:NSUTF8StringEncoding] 36 | options:0 37 | error:&error]; 38 | if (!error && [applinkData isKindOfClass:[NSDictionary class]]) { 39 | // If the version is not specified, assume it is 1. 40 | NSString *version = applinkData[BFAppLinkVersionKeyName] ?: @"1.0"; 41 | NSString *target = applinkData[BFAppLinkTargetKeyName]; 42 | if ([version isKindOfClass:[NSString class]] && 43 | [version isEqual:BFAppLinkVersion]) { 44 | // There's applink data! The target should actually be the applink target. 45 | _appLinkData = applinkData; 46 | id applinkExtras = applinkData[BFAppLinkExtrasKeyName]; 47 | if (applinkExtras && [applinkExtras isKindOfClass:[NSDictionary class]]) { 48 | _appLinkExtras = applinkExtras; 49 | } 50 | _targetURL = ([target isKindOfClass:[NSString class]] ? [NSURL URLWithString:target] : url); 51 | _targetQueryParameters = [BFURL queryParametersForURL:_targetURL]; 52 | 53 | NSDictionary *refererAppLink = _appLinkData[BFAppLinkRefererAppLink]; 54 | NSString *refererURLString = refererAppLink[BFAppLinkRefererUrl]; 55 | NSString *refererAppName = refererAppLink[BFAppLinkRefererAppName]; 56 | 57 | if (refererURLString && refererAppName) { 58 | BFAppLinkTarget *appLinkTarget = [BFAppLinkTarget appLinkTargetWithURL:[NSURL URLWithString:refererURLString] 59 | appStoreId:nil 60 | appName:refererAppName]; 61 | _appLinkReferer = [BFAppLink appLinkWithSourceURL:[NSURL URLWithString:refererURLString] 62 | targets:@[ appLinkTarget ] 63 | webURL:nil 64 | isBackToReferrer:YES]; 65 | } 66 | 67 | // Raise Measurement Event 68 | NSString *const EVENT_YES_VAL = @"1"; 69 | NSString *const EVENT_NO_VAL = @"0"; 70 | NSMutableDictionary *logData = [[NSMutableDictionary alloc] init]; 71 | logData[@"version"] = version; 72 | if (refererURLString) { 73 | logData[@"refererURL"] = refererURLString; 74 | } 75 | if (refererAppName) { 76 | logData[@"refererAppName"] = refererAppName; 77 | } 78 | if (sourceApplication) { 79 | logData[@"sourceApplication"] = sourceApplication; 80 | } 81 | if ([_targetURL absoluteString]) { 82 | logData[@"targetURL"] = [_targetURL absoluteString]; 83 | } 84 | if ([_inputURL absoluteString]) { 85 | logData[@"inputURL"] = [_inputURL absoluteString]; 86 | } 87 | if ([_inputURL scheme]) { 88 | logData[@"inputURLScheme"] = [_inputURL scheme]; 89 | } 90 | logData[@"forRenderBackToReferrerBar"] = forRenderBackToReferrerBar ? EVENT_YES_VAL : EVENT_NO_VAL; 91 | logData[@"forOpenUrl"] = forOpenURLEvent ? EVENT_YES_VAL : EVENT_NO_VAL; 92 | [BFMeasurementEvent postNotificationForEventName:BFAppLinkParseEventName args:logData]; 93 | if (forOpenURLEvent) { 94 | [BFMeasurementEvent postNotificationForEventName:BFAppLinkNavigateInEventName args:logData]; 95 | } 96 | } 97 | } 98 | } 99 | 100 | return self; 101 | } 102 | 103 | + (BFURL *)URLWithURL:(NSURL *)url { 104 | return [[BFURL alloc] initWithURL:url forOpenInboundURL:NO sourceApplication:nil forRenderBackToReferrerBar:NO]; 105 | } 106 | 107 | + (BFURL *)URLWithInboundURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication { 108 | return [[BFURL alloc] initWithURL:url forOpenInboundURL:YES sourceApplication:sourceApplication forRenderBackToReferrerBar:NO]; 109 | } 110 | 111 | + (BFURL *)URLForRenderBackToReferrerBarURL:(NSURL *)url { 112 | return [[BFURL alloc] initWithURL:url forOpenInboundURL:NO sourceApplication:nil forRenderBackToReferrerBar:YES]; 113 | } 114 | 115 | + (NSString *)decodeURLString:(NSString *)string { 116 | return (NSString *)CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapes(NULL, 117 | (CFStringRef)string, 118 | CFSTR(""))); 119 | } 120 | 121 | + (NSDictionary *)queryParametersForURL:(NSURL *)url { 122 | NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; 123 | NSString *query = url.query; 124 | if ([query isEqualToString:@""]) { 125 | return @{}; 126 | } 127 | NSArray *queryComponents = [query componentsSeparatedByString:@"&"]; 128 | for (NSString *component in queryComponents) { 129 | NSRange equalsLocation = [component rangeOfString:@"="]; 130 | if (equalsLocation.location == NSNotFound) { 131 | // There's no equals, so associate the key with NSNull 132 | parameters[[self decodeURLString:component]] = [NSNull null]; 133 | } else { 134 | NSString *key = [self decodeURLString:[component substringToIndex:equalsLocation.location]]; 135 | NSString *value = [self decodeURLString:[component substringFromIndex:equalsLocation.location + 1]]; 136 | parameters[key] = value; 137 | } 138 | } 139 | return [NSDictionary dictionaryWithDictionary:parameters]; 140 | } 141 | 142 | @end 143 | -------------------------------------------------------------------------------- /Bolts/iOS/BFWebViewAppLinkResolver.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | 13 | #import 14 | 15 | /*! 16 | A reference implementation for an App Link resolver that uses a hidden WKWebView 17 | to parse the HTML containing App Link metadata. 18 | */ 19 | @interface BFWebViewAppLinkResolver : NSObject 20 | 21 | /*! 22 | Gets the instance of a BFWebViewAppLinkResolver. 23 | */ 24 | + (instancetype)sharedInstance; 25 | 26 | @end 27 | 28 | -------------------------------------------------------------------------------- /Bolts/iOS/Internal/BFAppLinkReturnToRefererView_Internal.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | 13 | @interface BFAppLinkReturnToRefererView (Internal) 14 | 15 | - (CGFloat)statusBarHeight; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Bolts/iOS/Internal/BFAppLink_Internal.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | 13 | FOUNDATION_EXPORT NSString *const BFAppLinkDataParameterName; 14 | FOUNDATION_EXPORT NSString *const BFAppLinkTargetKeyName; 15 | FOUNDATION_EXPORT NSString *const BFAppLinkUserAgentKeyName; 16 | FOUNDATION_EXPORT NSString *const BFAppLinkExtrasKeyName; 17 | FOUNDATION_EXPORT NSString *const BFAppLinkVersionKeyName; 18 | FOUNDATION_EXPORT NSString *const BFAppLinkRefererAppLink; 19 | FOUNDATION_EXPORT NSString *const BFAppLinkRefererAppName; 20 | FOUNDATION_EXPORT NSString *const BFAppLinkRefererUrl; 21 | 22 | @interface BFAppLink (Internal) 23 | 24 | + (instancetype)appLinkWithSourceURL:(NSURL *)sourceURL 25 | targets:(NSArray *)targets 26 | webURL:(NSURL *)webURL 27 | isBackToReferrer:(BOOL)isBackToReferrer; 28 | 29 | /*! return if this AppLink is to go back to referrer. */ 30 | @property (nonatomic, assign, readonly, getter=isBackToReferrer) BOOL backToReferrer; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /Bolts/iOS/Internal/BFMeasurementEvent_Internal.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | /*! 13 | Provides methods for posting notifications from the Bolts framework 14 | */ 15 | @interface BFMeasurementEvent (Internal) 16 | 17 | + (void)postNotificationForEventName:(NSString *)name args:(NSDictionary *)args; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Bolts/iOS/Internal/BFURL_Internal.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | 13 | @interface BFURL (Internal) 14 | + (BFURL *)URLForRenderBackToReferrerBarURL:(NSURL *)url; 15 | @end 16 | -------------------------------------------------------------------------------- /BoltsTestUI/AppDelegate.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | 13 | @interface AppDelegate : UIResponder 14 | 15 | @property (strong, nonatomic) UIWindow *window; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /BoltsTestUI/AppDelegate.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import "AppDelegate.h" 12 | 13 | @implementation AppDelegate 14 | 15 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 16 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 17 | // Override point for customization after application launch. 18 | self.window.backgroundColor = [UIColor whiteColor]; 19 | [self.window makeKeyAndVisible]; 20 | [self.window setRootViewController:[[UIViewController alloc] init]]; 21 | return YES; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /BoltsTestUI/BoltsTestUI-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleURLTypes 24 | 25 | 26 | CFBundleTypeRole 27 | Editor 28 | CFBundleURLSchemes 29 | 30 | bolts 31 | 32 | 33 | 34 | CFBundleTypeRole 35 | Editor 36 | CFBundleURLSchemes 37 | 38 | bolts2 39 | 40 | 41 | 42 | LSApplicationQueriesSchemes 43 | 44 | bolts 45 | 46 | CFBundleVersion 47 | 1.0 48 | LSRequiresIPhoneOS 49 | 50 | UIRequiredDeviceCapabilities 51 | 52 | armv7 53 | 54 | UISupportedInterfaceOrientations 55 | 56 | UIInterfaceOrientationPortrait 57 | UIInterfaceOrientationLandscapeLeft 58 | UIInterfaceOrientationLandscapeRight 59 | 60 | UISupportedInterfaceOrientations~ipad 61 | 62 | UIInterfaceOrientationPortrait 63 | UIInterfaceOrientationPortraitUpsideDown 64 | UIInterfaceOrientationLandscapeLeft 65 | UIInterfaceOrientationLandscapeRight 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /BoltsTestUI/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "60x60", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "60x60", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "ipad", 25 | "size" : "29x29", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "ipad", 30 | "size" : "29x29", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "40x40", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "40x40", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "76x76", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "76x76", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /BoltsTestUI/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "orientation" : "portrait", 20 | "idiom" : "ipad", 21 | "extent" : "full-screen", 22 | "minimum-system-version" : "7.0", 23 | "scale" : "1x" 24 | }, 25 | { 26 | "orientation" : "landscape", 27 | "idiom" : "ipad", 28 | "extent" : "full-screen", 29 | "minimum-system-version" : "7.0", 30 | "scale" : "1x" 31 | }, 32 | { 33 | "orientation" : "portrait", 34 | "idiom" : "ipad", 35 | "extent" : "full-screen", 36 | "minimum-system-version" : "7.0", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "orientation" : "landscape", 41 | "idiom" : "ipad", 42 | "extent" : "full-screen", 43 | "minimum-system-version" : "7.0", 44 | "scale" : "2x" 45 | } 46 | ], 47 | "info" : { 48 | "version" : 1, 49 | "author" : "xcode" 50 | } 51 | } -------------------------------------------------------------------------------- /BoltsTestUI/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /BoltsTestUI/main.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | #import 12 | 13 | #import "AppDelegate.h" 14 | 15 | int main(int argc, char * argv[]) 16 | { 17 | @autoreleasepool { 18 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /BoltsTestUI/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /BoltsTests/AppLinkReturnToRefererViewTests.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | @import XCTest; 12 | 13 | #import 14 | 15 | static NSString *const BFURLWithRefererData = @"bolts://?foo=bar&al_applink_data=%7B%22a%22%3A%22b%22%2C%22user_agent%22%3A%22Bolts%20iOS%201.0.0%22%2C%22target_url%22%3A%22http%3A%5C%2F%5C%2Fwww.example.com%5C%2Fpath%3Fbaz%3Dbat%22%2C%22referer_app_link%22%3A%7B%22app_name%22%3A%22Facebook%22%2C%22url%22%3A%22fb%3A%5C%2F%5C%2Fsomething%5C%2F%22%7D%7D"; 16 | static NSString *const BFURLWithRefererUrlNoName = @"bolts://?foo=bar&al_applink_data=%7B%22a%22%3A%22b%22%2C%22user_agent%22%3A%22Bolts%20iOS%201.0.0%22%2C%22target_url%22%3A%22http%3A%5C%2F%5C%2Fwww.example.com%5C%2Fpath%3Fbaz%3Dbat%22%2C%22referer_app_link%22%3A%7B%22url%22%3A%22fb%3A%5C%2F%5C%2Fsomething%5C%2F%22%7D%7D"; 17 | static NSString *const BFURLWithRefererNameNoUrl = @"bolts://?foo=bar&al_applink_data=%7B%22a%22%3A%22b%22%2C%22user_agent%22%3A%22Bolts%20iOS%201.0.0%22%2C%22target_url%22%3A%22http%3A%5C%2F%5C%2Fwww.example.com%5C%2Fpath%3Fbaz%3Dbat%22%2C%22referer_app_link%22%3A%7B%22app_name%22%3A%22Facebook%22%7D%7D"; 18 | 19 | @interface AppLinkReturnToRefererViewTests : XCTestCase 20 | 21 | @end 22 | 23 | @implementation AppLinkReturnToRefererViewTests 24 | 25 | - (void)testInitReturnsValidView { 26 | BFAppLinkReturnToRefererView *view = [[BFAppLinkReturnToRefererView alloc] init]; 27 | 28 | XCTAssert(view); 29 | } 30 | 31 | - (void)testNoRefererDataResultsInZeroHeight { 32 | BFAppLinkReturnToRefererView *view = [[BFAppLinkReturnToRefererView alloc] init]; 33 | 34 | CGSize sizeThatFits = [view sizeThatFits:CGSizeMake(100.0, 100.0)]; 35 | 36 | XCTAssertEqualWithAccuracy(0.0, sizeThatFits.height, FLT_EPSILON); 37 | } 38 | 39 | - (void)testNoRefererNameResultsInZeroHeight { 40 | NSURL *url = [NSURL URLWithString:BFURLWithRefererUrlNoName]; 41 | BFAppLink *appLink = [[BFURL URLWithURL:url] appLinkReferer]; 42 | 43 | BFAppLinkReturnToRefererView *view = [[BFAppLinkReturnToRefererView alloc] init]; 44 | view.refererAppLink = appLink; 45 | 46 | CGSize sizeThatFits = [view sizeThatFits:CGSizeMake(100.0, 100.0)]; 47 | 48 | XCTAssertEqualWithAccuracy(0.0, sizeThatFits.height, FLT_EPSILON); 49 | } 50 | 51 | - (void)testNoRefererUrlResultsInZeroHeight { 52 | NSURL *url = [NSURL URLWithString:BFURLWithRefererNameNoUrl]; 53 | BFAppLink *appLink = [[BFURL URLWithURL:url] appLinkReferer]; 54 | 55 | BFAppLinkReturnToRefererView *view = [[BFAppLinkReturnToRefererView alloc] init]; 56 | view.refererAppLink = appLink; 57 | 58 | CGSize sizeThatFits = [view sizeThatFits:CGSizeMake(100.0, 100.0)]; 59 | 60 | XCTAssertEqualWithAccuracy(0.0, sizeThatFits.height, FLT_EPSILON); 61 | } 62 | 63 | - (void)testValidRefererDataResultsInNonZeroSizeThatFits { 64 | NSURL *url = [NSURL URLWithString:BFURLWithRefererData]; 65 | BFAppLink *appLink = [[BFURL URLWithURL:url] appLinkReferer]; 66 | 67 | BFAppLinkReturnToRefererView *view = [[BFAppLinkReturnToRefererView alloc] init]; 68 | view.refererAppLink = appLink; 69 | 70 | CGSize sizeThatFits = [view sizeThatFits:CGSizeMake(100.0, 100.0)]; 71 | 72 | XCTAssert(sizeThatFits.height > 0.0); 73 | XCTAssert(sizeThatFits.width > 0.0); 74 | } 75 | 76 | - (void)testIncludesStatusBarResultsInLargerHeight { 77 | NSURL *url = [NSURL URLWithString:BFURLWithRefererData]; 78 | BFAppLink *appLink = [[BFURL URLWithURL:url] appLinkReferer]; 79 | 80 | BFAppLinkReturnToRefererView *view = [[BFAppLinkReturnToRefererView alloc] init]; 81 | view.refererAppLink = appLink; 82 | view.includeStatusBarInSize = BFIncludeStatusBarInSizeNever; 83 | CGSize sizeThatFitsNotIncludingStatusBar = [view sizeThatFits:CGSizeMake(100.0, 100.0)]; 84 | 85 | view.includeStatusBarInSize = BFIncludeStatusBarInSizeAlways; 86 | CGSize sizeThatFitsIncludingStatusBar = [view sizeThatFits:CGSizeMake(100.0, 100.0)]; 87 | 88 | XCTAssert(sizeThatFitsIncludingStatusBar.height > sizeThatFitsNotIncludingStatusBar.height); 89 | } 90 | 91 | - (void)testNotIncludingStatusBarResultsInSmallerHeight { 92 | NSURL *url = [NSURL URLWithString:BFURLWithRefererData]; 93 | BFAppLink *appLink = [[BFURL URLWithURL:url] appLinkReferer]; 94 | 95 | BFAppLinkReturnToRefererView *view = [[BFAppLinkReturnToRefererView alloc] init]; 96 | view.refererAppLink = appLink; 97 | CGSize sizeThatFitsIncludingStatusBar = [view sizeThatFits:CGSizeMake(100.0, 100.0)]; 98 | 99 | view.includeStatusBarInSize = BFIncludeStatusBarInSizeNever; 100 | CGSize sizeThatFitsNotIncludingStatusBar = [view sizeThatFits:CGSizeMake(100.0, 100.0)]; 101 | 102 | XCTAssert(sizeThatFitsIncludingStatusBar.height > sizeThatFitsNotIncludingStatusBar.height); 103 | } 104 | 105 | @end 106 | -------------------------------------------------------------------------------- /BoltsTests/BoltsTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /BoltsTests/CancellationTests.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | @import XCTest; 12 | 13 | #import 14 | 15 | @interface CancellationTests : XCTestCase 16 | @end 17 | 18 | @implementation CancellationTests 19 | 20 | - (void)testCancel { 21 | BFCancellationTokenSource *cts = [BFCancellationTokenSource cancellationTokenSource]; 22 | 23 | XCTAssertFalse(cts.cancellationRequested, @"Source should not be cancelled"); 24 | XCTAssertFalse(cts.token.cancellationRequested, @"Token should not be cancelled"); 25 | 26 | [cts cancel]; 27 | 28 | XCTAssertTrue(cts.cancellationRequested, @"Source should be cancelled"); 29 | XCTAssertTrue(cts.token.cancellationRequested, @"Token should be cancelled"); 30 | } 31 | 32 | - (void)testCancelMultipleTimes { 33 | BFCancellationTokenSource *cts = [BFCancellationTokenSource cancellationTokenSource]; 34 | XCTAssertFalse(cts.cancellationRequested); 35 | XCTAssertFalse(cts.token.cancellationRequested); 36 | 37 | [cts cancel]; 38 | XCTAssertTrue(cts.cancellationRequested); 39 | XCTAssertTrue(cts.token.cancellationRequested); 40 | 41 | [cts cancel]; 42 | XCTAssertTrue(cts.cancellationRequested); 43 | XCTAssertTrue(cts.token.cancellationRequested); 44 | } 45 | 46 | - (void)testCancellationBlock { 47 | __block BOOL cancelled = NO; 48 | 49 | BFCancellationTokenSource *cts = [BFCancellationTokenSource cancellationTokenSource]; 50 | [cts.token registerCancellationObserverWithBlock:^{ 51 | cancelled = YES; 52 | }]; 53 | 54 | XCTAssertFalse(cts.cancellationRequested, @"Source should not be cancelled"); 55 | XCTAssertFalse(cts.token.cancellationRequested, @"Token should not be cancelled"); 56 | 57 | [cts cancel]; 58 | 59 | XCTAssertTrue(cancelled, @"Source should be cancelled"); 60 | } 61 | 62 | - (void)testCancellationAfterDelay { 63 | BFCancellationTokenSource *cts = [BFCancellationTokenSource cancellationTokenSource]; 64 | 65 | XCTAssertFalse(cts.cancellationRequested, @"Source should not be cancelled"); 66 | XCTAssertFalse(cts.token.cancellationRequested, @"Token should not be cancelled"); 67 | 68 | [cts cancelAfterDelay:200]; 69 | XCTAssertFalse(cts.cancellationRequested, @"Source should be cancelled"); 70 | XCTAssertFalse(cts.token.cancellationRequested, @"Token should be cancelled"); 71 | 72 | // Spin the run loop for half a second, since `delay` is in milliseconds, not seconds. 73 | [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5]]; 74 | 75 | XCTAssertTrue(cts.cancellationRequested, @"Source should be cancelled"); 76 | XCTAssertTrue(cts.token.cancellationRequested, @"Token should be cancelled"); 77 | } 78 | 79 | - (void)testCancellationAfterDelayValidation { 80 | BFCancellationTokenSource *cts = [BFCancellationTokenSource cancellationTokenSource]; 81 | 82 | XCTAssertFalse(cts.cancellationRequested); 83 | XCTAssertFalse(cts.token.cancellationRequested); 84 | 85 | XCTAssertThrowsSpecificNamed([cts cancelAfterDelay:-2], NSException, NSInvalidArgumentException); 86 | } 87 | 88 | - (void)testCancellationAfterZeroDelay { 89 | BFCancellationTokenSource *cts = [BFCancellationTokenSource cancellationTokenSource]; 90 | 91 | XCTAssertFalse(cts.cancellationRequested); 92 | XCTAssertFalse(cts.token.cancellationRequested); 93 | 94 | [cts cancelAfterDelay:0]; 95 | 96 | XCTAssertTrue(cts.cancellationRequested); 97 | XCTAssertTrue(cts.token.cancellationRequested); 98 | } 99 | 100 | - (void)testCancellationAfterDelayOnCancelled { 101 | BFCancellationTokenSource *cts = [BFCancellationTokenSource cancellationTokenSource]; 102 | [cts cancel]; 103 | XCTAssertTrue(cts.cancellationRequested); 104 | XCTAssertTrue(cts.token.cancellationRequested); 105 | 106 | [cts cancelAfterDelay:1]; 107 | 108 | XCTAssertTrue(cts.cancellationRequested); 109 | XCTAssertTrue(cts.token.cancellationRequested); 110 | } 111 | 112 | - (void)testDispose { 113 | BFCancellationTokenSource *cts = [BFCancellationTokenSource cancellationTokenSource]; 114 | [cts dispose]; 115 | XCTAssertThrowsSpecificNamed([cts cancel], NSException, NSInternalInconsistencyException); 116 | XCTAssertThrowsSpecificNamed(cts.cancellationRequested, NSException, NSInternalInconsistencyException); 117 | XCTAssertThrowsSpecificNamed(cts.token.cancellationRequested, NSException, NSInternalInconsistencyException); 118 | 119 | cts = [BFCancellationTokenSource cancellationTokenSource]; 120 | [cts cancel]; 121 | XCTAssertTrue(cts.cancellationRequested, @"Source should be cancelled"); 122 | XCTAssertTrue(cts.token.cancellationRequested, @"Token should be cancelled"); 123 | 124 | [cts dispose]; 125 | XCTAssertThrowsSpecificNamed(cts.cancellationRequested, NSException, NSInternalInconsistencyException); 126 | XCTAssertThrowsSpecificNamed(cts.token.cancellationRequested, NSException, NSInternalInconsistencyException); 127 | } 128 | 129 | - (void)testDisposeMultipleTimes { 130 | BFCancellationTokenSource *cts = [BFCancellationTokenSource cancellationTokenSource]; 131 | [cts dispose]; 132 | XCTAssertNoThrow([cts dispose]); 133 | } 134 | 135 | - (void)testDisposeRegistration { 136 | BFCancellationTokenSource *cts = [BFCancellationTokenSource cancellationTokenSource]; 137 | BFCancellationTokenRegistration *registration = [cts.token registerCancellationObserverWithBlock:^{ 138 | XCTFail(); 139 | }]; 140 | XCTAssertNoThrow([registration dispose]); 141 | 142 | [cts cancel]; 143 | } 144 | 145 | - (void)testDisposeRegistrationMultipleTimes { 146 | BFCancellationTokenSource *cts = [BFCancellationTokenSource cancellationTokenSource]; 147 | BFCancellationTokenRegistration *registration = [cts.token registerCancellationObserverWithBlock:^{ 148 | XCTFail(); 149 | }]; 150 | XCTAssertNoThrow([registration dispose]); 151 | XCTAssertNoThrow([registration dispose]); 152 | 153 | [cts cancel]; 154 | } 155 | 156 | - (void)testDisposeRegistrationAfterCancellationToken { 157 | BFCancellationTokenSource *cts = [BFCancellationTokenSource cancellationTokenSource]; 158 | BFCancellationTokenRegistration *registration = [cts.token registerCancellationObserverWithBlock:^{ }]; 159 | 160 | [registration dispose]; 161 | [cts dispose]; 162 | } 163 | 164 | - (void)testDisposeRegistrationBeforeCancellationToken { 165 | BFCancellationTokenSource *cts = [BFCancellationTokenSource cancellationTokenSource]; 166 | BFCancellationTokenRegistration *registration = [cts.token registerCancellationObserverWithBlock:^{ }]; 167 | 168 | [cts dispose]; 169 | XCTAssertNoThrow([registration dispose]); 170 | } 171 | 172 | @end 173 | -------------------------------------------------------------------------------- /BoltsTests/ExecutorTests.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | @import XCTest; 12 | 13 | #import 14 | 15 | @interface ExecutorTests : XCTestCase 16 | 17 | @end 18 | 19 | @implementation ExecutorTests 20 | 21 | - (void)testExecuteImmediately { 22 | __block BFTask *task = [BFTask taskWithResult:nil]; 23 | 24 | XCTestExpectation *expectation = [self expectationWithDescription:@"test immediate executor"]; 25 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 26 | task = [task continueWithExecutor:[BFExecutor immediateExecutor] withBlock:^id(BFTask *_) { 27 | return nil; 28 | }]; 29 | XCTAssertTrue(task.completed); 30 | [expectation fulfill]; 31 | }); 32 | [self waitForExpectationsWithTimeout:10.0 handler:nil]; 33 | } 34 | 35 | - (void)testExecuteOnDispatchQueue { 36 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0L); 37 | BFExecutor *queueExecutor = [BFExecutor executorWithDispatchQueue:queue]; 38 | 39 | BFTask *task = [BFTask taskWithResult:nil]; 40 | task = [task continueWithExecutor:queueExecutor withBlock:^id(BFTask *_) { 41 | #pragma clang diagnostic push 42 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 43 | XCTAssertEqual(queue, dispatch_get_current_queue()); 44 | #pragma clang diagnostic pop 45 | return nil; 46 | }]; 47 | [task waitUntilFinished]; 48 | } 49 | 50 | - (void)testExecuteOnOperationQueue { 51 | NSOperationQueue *queue = [[NSOperationQueue alloc] init]; 52 | BFExecutor *queueExecutor = [BFExecutor executorWithOperationQueue:queue]; 53 | 54 | BFTask *task = [BFTask taskWithResult:nil]; 55 | task = [task continueWithExecutor:queueExecutor withBlock:^id(BFTask *_) { 56 | XCTAssertEqual(queue, [NSOperationQueue currentQueue]); 57 | return nil; 58 | }]; 59 | [task waitUntilFinished]; 60 | } 61 | 62 | - (void)testMainThreadExecutor { 63 | BFExecutor *executor = [BFExecutor mainThreadExecutor]; 64 | 65 | XCTestExpectation *immediateExpectation = [self expectationWithDescription:@"test main thread executor on main thread"]; 66 | [executor execute:^{ 67 | XCTAssertTrue([NSThread isMainThread]); 68 | [immediateExpectation fulfill]; 69 | }]; 70 | 71 | // Behaviour is different when running on main thread (runs immediately) vs running on the background queue. 72 | XCTestExpectation *backgroundExpectation = [self expectationWithDescription:@"test main thread executor on background thread"]; 73 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 74 | [executor execute:^{ 75 | XCTAssertTrue([NSThread isMainThread]); 76 | [backgroundExpectation fulfill]; 77 | }]; 78 | }); 79 | 80 | [self waitForExpectationsWithTimeout:10.0 handler:nil]; 81 | } 82 | 83 | @end 84 | -------------------------------------------------------------------------------- /BoltsTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | Facebook has adopted a Code of Conduct that we expect project participants to adhere to. Please [read the full text](https://code.fb.com/codeofconduct) so that you can understand what actions will and will not be tolerated. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Bolts 2 | We want to make contributing to this project as easy and transparent as 3 | possible. 4 | 5 | ## Pull Requests 6 | We actively welcome your pull requests. 7 | 8 | 1. Fork the repo and create your branch from `master`. 9 | 2. If you've added code that should be tested, add tests. 10 | 3. If you've changed APIs, update the documentation. 11 | 4. Ensure the test suite passes. 12 | 5. Make sure your code lints. 13 | 6. If you haven't already, complete the Contributor License Agreement ("CLA"). 14 | 15 | ## Contributor License Agreement ("CLA") 16 | In order to accept your pull request, we need you to submit a CLA. You only need 17 | to do this once to work on any of Facebook's open source projects. 18 | 19 | Complete your CLA here: 20 | 21 | ## Issues 22 | We use GitHub issues to track public bugs. Please ensure your description is 23 | clear and has sufficient instructions to be able to reproduce the issue. 24 | 25 | Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe 26 | disclosure of security bugs. In those cases, please go through the process 27 | outlined on that page and do not file a public issue. 28 | 29 | ## Coding Style 30 | * Most importantly, match the existing code style as much as possible. 31 | * Try to keep lines under 100 characters, if possible. 32 | 33 | ## License 34 | By contributing to Bolts, you agree that your contributions will be licensed 35 | under its BSD license. 36 | -------------------------------------------------------------------------------- /Configurations/Bolts-iOS-Dynamic.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2014, Facebook, Inc. 3 | // All rights reserved. 4 | // 5 | // This source code is licensed under the BSD-style license found in the 6 | // LICENSE file in the root directory of this source tree. An additional grant 7 | // of patent rights can be found in the PATENTS file in the same directory. 8 | // 9 | 10 | #include "Shared/Platform/iOS.xcconfig" 11 | #include "Shared/Product/DynamicFramework.xcconfig" 12 | #include "Version.xcconfig" 13 | 14 | PRODUCT_NAME = Bolts 15 | PRODUCT_BUNDLE_IDENTIFIER = com.bolts.ios 16 | IPHONEOS_DEPLOYMENT_TARGET = 8.0 17 | 18 | MODULEMAP_FILE = $(SRCROOT)/Bolts/Resources/iOS.modulemap 19 | INFOPLIST_FILE = $(SRCROOT)/Bolts/Resources/Info.plist 20 | 21 | APPLICATION_EXTENSION_API_ONLY = YES 22 | 23 | OTHER_LDFLAGS = $(inherited) -framework CoreGraphics -framework UIKit 24 | 25 | // Use the 1.0.0 to avoid breaking changes with older version of Bolts. 26 | DYLIB_COMPATIBILITY_VERSION = 1 27 | -------------------------------------------------------------------------------- /Configurations/Bolts-iOS.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2014, Facebook, Inc. 3 | // All rights reserved. 4 | // 5 | // This source code is licensed under the BSD-style license found in the 6 | // LICENSE file in the root directory of this source tree. An additional grant 7 | // of patent rights can be found in the PATENTS file in the same directory. 8 | // 9 | 10 | #include "Shared/Platform/iOS.xcconfig" 11 | #include "Shared/Product/StaticFramework.xcconfig" 12 | #include "Version.xcconfig" 13 | 14 | PRODUCT_NAME = Bolts 15 | PRODUCT_BUNDLE_IDENTIFIER = com.bolts.ios 16 | IPHONEOS_DEPLOYMENT_TARGET = 8.0 17 | 18 | MODULEMAP_FILE = $(SRCROOT)/Bolts/Resources/iOS.modulemap 19 | INFOPLIST_FILE = $(SRCROOT)/Bolts/Resources/Info.plist 20 | 21 | APPLICATION_EXTENSION_API_ONLY = YES 22 | -------------------------------------------------------------------------------- /Configurations/Bolts-macOS.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2014, Facebook, Inc. 3 | // All rights reserved. 4 | // 5 | // This source code is licensed under the BSD-style license found in the 6 | // LICENSE file in the root directory of this source tree. An additional grant 7 | // of patent rights can be found in the PATENTS file in the same directory. 8 | // 9 | 10 | #include "Shared/Platform/macOS.xcconfig" 11 | #include "Shared/Product/DynamicFramework.xcconfig" 12 | #include "Version.xcconfig" 13 | 14 | PRODUCT_NAME = Bolts 15 | PRODUCT_BUNDLE_IDENTIFIER = com.bolts.macos 16 | MACOSX_DEPLOYMENT_TARGET = 10.8 17 | 18 | INFOPLIST_FILE = $(SRCROOT)/Bolts/Resources/Info.plist 19 | 20 | // Use the 1.0.0 to avoid breaking changes with older version of Bolts. 21 | DYLIB_COMPATIBILITY_VERSION = 1 22 | 23 | APPLICATION_EXTENSION_API_ONLY = YES 24 | -------------------------------------------------------------------------------- /Configurations/Bolts-tvOS-Dynamic.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2014, Facebook, Inc. 3 | // All rights reserved. 4 | // 5 | // This source code is licensed under the BSD-style license found in the 6 | // LICENSE file in the root directory of this source tree. An additional grant 7 | // of patent rights can be found in the PATENTS file in the same directory. 8 | // 9 | 10 | #include "Shared/Platform/tvOS.xcconfig" 11 | #include "Shared/Product/DynamicFramework.xcconfig" 12 | #include "Version.xcconfig" 13 | 14 | PRODUCT_NAME = Bolts 15 | PRODUCT_BUNDLE_IDENTIFIER = com.bolts.tvos 16 | 17 | INFOPLIST_FILE = $(SRCROOT)/Bolts/Resources/Info.plist 18 | 19 | // Use the 1.0.0 to avoid breaking changes with older version of Bolts. 20 | DYLIB_COMPATIBILITY_VERSION = 1 21 | 22 | APPLICATION_EXTENSION_API_ONLY = YES 23 | -------------------------------------------------------------------------------- /Configurations/Bolts-tvOS.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2014, Facebook, Inc. 3 | // All rights reserved. 4 | // 5 | // This source code is licensed under the BSD-style license found in the 6 | // LICENSE file in the root directory of this source tree. An additional grant 7 | // of patent rights can be found in the PATENTS file in the same directory. 8 | // 9 | 10 | #include "Shared/Platform/tvOS.xcconfig" 11 | #include "Shared/Product/StaticFramework.xcconfig" 12 | #include "Version.xcconfig" 13 | 14 | PRODUCT_NAME = Bolts 15 | PRODUCT_BUNDLE_IDENTIFIER = com.bolts.tvos 16 | 17 | INFOPLIST_FILE = $(SRCROOT)/Bolts/Resources/Info.plist 18 | 19 | APPLICATION_EXTENSION_API_ONLY = YES 20 | -------------------------------------------------------------------------------- /Configurations/Bolts-watchOS-Dynamic.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2014, Facebook, Inc. 3 | // All rights reserved. 4 | // 5 | // This source code is licensed under the BSD-style license found in the 6 | // LICENSE file in the root directory of this source tree. An additional grant 7 | // of patent rights can be found in the PATENTS file in the same directory. 8 | // 9 | 10 | #include "Shared/Platform/watchOS.xcconfig" 11 | #include "Shared/Product/DynamicFramework.xcconfig" 12 | #include "Version.xcconfig" 13 | 14 | PRODUCT_NAME = Bolts 15 | PRODUCT_BUNDLE_IDENTIFIER = com.bolts.watchos 16 | WATCHOS_DEPLOYMENT_TARGET = 2.0 17 | 18 | INFOPLIST_FILE = $(SRCROOT)/Bolts/Resources/Info.plist 19 | 20 | // Use the 1.0.0 to avoid breaking changes with older version of Bolts. 21 | DYLIB_COMPATIBILITY_VERSION = 1 22 | 23 | APPLICATION_EXTENSION_API_ONLY = YES 24 | -------------------------------------------------------------------------------- /Configurations/Bolts-watchOS.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2014, Facebook, Inc. 3 | // All rights reserved. 4 | // 5 | // This source code is licensed under the BSD-style license found in the 6 | // LICENSE file in the root directory of this source tree. An additional grant 7 | // of patent rights can be found in the PATENTS file in the same directory. 8 | // 9 | 10 | #include "Shared/Platform/watchOS.xcconfig" 11 | #include "Shared/Product/StaticFramework.xcconfig" 12 | #include "Version.xcconfig" 13 | 14 | PRODUCT_NAME = Bolts 15 | PRODUCT_BUNDLE_IDENTIFIER = com.bolts.watchos 16 | WATCHOS_DEPLOYMENT_TARGET = 2.0 17 | 18 | INFOPLIST_FILE = $(SRCROOT)/Bolts/Resources/Info.plist 19 | 20 | APPLICATION_EXTENSION_API_ONLY = YES 21 | -------------------------------------------------------------------------------- /Configurations/BoltsTests-OSX.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2014, Facebook, Inc. 3 | // All rights reserved. 4 | // 5 | // This source code is licensed under the BSD-style license found in the 6 | // LICENSE file in the root directory of this source tree. An additional grant 7 | // of patent rights can be found in the PATENTS file in the same directory. 8 | // 9 | 10 | #include "Shared/Platform/macOS.xcconfig" 11 | #include "Shared/Product/LogicTests.xcconfig" 12 | 13 | PRODUCT_NAME = BoltsTests-OSX 14 | PRODUCT_MODULE_NAME = BoltsTests 15 | PRODUCT_BUNDLE_IDENTIFIER = com.bolts.osx.tests 16 | 17 | MACOSX_DEPLOYMENT_TARGET = 10.8 18 | 19 | INFOPLIST_FILE = BoltsTests/BoltsTests-Info.plist 20 | -------------------------------------------------------------------------------- /Configurations/BoltsTests-iOS.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2014, Facebook, Inc. 3 | // All rights reserved. 4 | // 5 | // This source code is licensed under the BSD-style license found in the 6 | // LICENSE file in the root directory of this source tree. An additional grant 7 | // of patent rights can be found in the PATENTS file in the same directory. 8 | // 9 | 10 | #include "Shared/Platform/iOS.xcconfig" 11 | #include "Shared/Product/LogicTests.xcconfig" 12 | 13 | PRODUCT_NAME = BoltsTests-iOS 14 | PRODUCT_MODULE_NAME = BoltsTests 15 | PRODUCT_BUNDLE_IDENTIFIER = com.bolts.ios.tests 16 | 17 | IPHONEOS_DEPLOYMENT_TARGET = 8.0 18 | 19 | INFOPLIST_FILE = BoltsTests/BoltsTests-Info.plist 20 | 21 | TEST_HOST = $(BUILT_PRODUCTS_DIR)/BoltsTestUI.app/BoltsTestUI 22 | -------------------------------------------------------------------------------- /Configurations/BoltsTests-tvOS.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2014, Facebook, Inc. 3 | // All rights reserved. 4 | // 5 | // This source code is licensed under the BSD-style license found in the 6 | // LICENSE file in the root directory of this source tree. An additional grant 7 | // of patent rights can be found in the PATENTS file in the same directory. 8 | // 9 | 10 | #include "Shared/Platform/tvOS.xcconfig" 11 | #include "Shared/Product/LogicTests.xcconfig" 12 | 13 | PRODUCT_NAME = BoltsTests-tvOS 14 | PRODUCT_MODULE_NAME = BoltsTests 15 | PRODUCT_BUNDLE_IDENTIFIER = com.bolts.tvos.tests 16 | 17 | INFOPLIST_FILE = BoltsTests/BoltsTests-Info.plist 18 | -------------------------------------------------------------------------------- /Configurations/Shared: -------------------------------------------------------------------------------- 1 | ../Vendor/xctoolchain/Configurations/ -------------------------------------------------------------------------------- /Configurations/Version.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2014, Facebook, Inc. 3 | // All rights reserved. 4 | // 5 | // This source code is licensed under the BSD-style license found in the 6 | // LICENSE file in the root directory of this source tree. An additional grant 7 | // of patent rights can be found in the PATENTS file in the same directory. 8 | // 9 | 10 | BOLTS_OBJC_VERSION = 1.9.1 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | For Bolts software 4 | 5 | Copyright (c) 2013-present, Facebook, Inc. All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name Facebook nor the names of its contributors may be used to 18 | endorse or promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 25 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 28 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /PATENTS: -------------------------------------------------------------------------------- 1 | Additional Grant of Patent Rights Version 2 2 | 3 | "Software" means the Bolts software distributed by Facebook, Inc. 4 | 5 | Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software 6 | ("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable 7 | (subject to the termination provision below) license under any Necessary 8 | Claims, to make, have made, use, sell, offer to sell, import, and otherwise 9 | transfer the Software. For avoidance of doubt, no license is granted under 10 | Facebook’s rights in any patent claims that are infringed by (i) modifications 11 | to the Software made by you or any third party or (ii) the Software in 12 | combination with any software or other technology. 13 | 14 | The license granted hereunder will terminate, automatically and without notice, 15 | if you (or any of your subsidiaries, corporate affiliates or agents) initiate 16 | directly or indirectly, or take a direct financial interest in, any Patent 17 | Assertion: (i) against Facebook or any of its subsidiaries or corporate 18 | affiliates, (ii) against any party if such Patent Assertion arises in whole or 19 | in part from any software, technology, product or service of Facebook or any of 20 | its subsidiaries or corporate affiliates, or (iii) against any party relating 21 | to the Software. Notwithstanding the foregoing, if Facebook or any of its 22 | subsidiaries or corporate affiliates files a lawsuit alleging patent 23 | infringement against you in the first instance, and you respond by filing a 24 | patent infringement counterclaim in that lawsuit against that party that is 25 | unrelated to the Software, the license granted hereunder will not terminate 26 | under section (i) of this paragraph due to such counterclaim. 27 | 28 | A "Necessary Claim" is a claim of a patent owned by Facebook that is 29 | necessarily infringed by the Software standing alone. 30 | 31 | A "Patent Assertion" is any lawsuit or other action alleging direct, indirect, 32 | or contributory infringement or inducement to infringe any patent, including a 33 | cross-claim or counterclaim. -------------------------------------------------------------------------------- /scripts/build_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright 2010-present Facebook. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | # This script builds Bolts.framework and the distribution package. 19 | 20 | . ${BOLTS_SCRIPT:-$(dirname $0)}/common.sh 21 | 22 | # ----------------------------------------------------------------------------- 23 | # Call out to build .framework 24 | # 25 | . $BOLTS_SCRIPT/build_framework.sh 26 | 27 | # ----------------------------------------------------------------------------- 28 | # Build docs 29 | # 30 | . $BOLTS_SCRIPT/build_documentation.sh 31 | 32 | common_success 33 | -------------------------------------------------------------------------------- /scripts/build_framework.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright 2010-present Facebook. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | # This script builds Bolts.framework. 19 | 20 | . ${BOLTS_SCRIPT:-$(dirname $0)}/common.sh 21 | 22 | # process options, valid arguments -c [Debug|Release] -n 23 | BUILDCONFIGURATION=Debug 24 | NOEXTRAS=1 25 | WATCHOS=0 26 | TVOS=0 27 | while getopts ":ntc:-:" OPTNAME 28 | do 29 | case "$OPTNAME" in 30 | "c") 31 | BUILDCONFIGURATION=$OPTARG 32 | ;; 33 | "n") 34 | NOEXTRAS=1 35 | ;; 36 | "t") 37 | NOEXTRAS=0 38 | ;; 39 | -) 40 | case "${OPTARG}" in 41 | "with-watchos") 42 | WATCHOS=1 43 | ;; 44 | "with-tvos") 45 | TVOS=1 46 | ;; 47 | *) 48 | # Should not occur 49 | echo "Unknown error while processing options" 50 | die 51 | ;; 52 | esac 53 | ;; 54 | "?") 55 | echo "$0 -c [Debug|Release] -n --with-watchos --with-tvos" 56 | echo " -c sets configuration (default=Debug)" 57 | echo " -n no test run (default)" 58 | echo " -t test run" 59 | echo " --with-watchos Add watchOS framework" 60 | echo " --with-tvos Add tvOS framework" 61 | die 62 | ;; 63 | ":") 64 | echo "Missing argument value for option $OPTARG" 65 | die 66 | ;; 67 | *) 68 | # Should not occur 69 | echo "Unknown error while processing options" 70 | die 71 | ;; 72 | esac 73 | done 74 | 75 | test -x "$XCODEBUILD" || die 'Could not find xcodebuild in $PATH' 76 | test -x "$LIPO" || die 'Could not find lipo in $PATH' 77 | 78 | BOLTS_IOS_BINARY=$BOLTS_BUILD/${BUILDCONFIGURATION}-universal/Bolts.framework/Bolts 79 | BOLTS_MACOS_BINARY=$BOLTS_BUILD/${BUILDCONFIGURATION}/Bolts.framework 80 | BOLTS_TVOS_BINARY=$BOLTS_BUILD/${BUILDCONFIGURATION}-appletv-universal/Bolts.framework/Bolts 81 | BOLTS_WATCHOS_BINARY=$BOLTS_BUILD/${BUILDCONFIGURATION}-watch-universal/Bolts.framework/Bolts 82 | 83 | # ----------------------------------------------------------------------------- 84 | 85 | progress_message Building Framework. 86 | 87 | # ----------------------------------------------------------------------------- 88 | # Compile binaries 89 | # 90 | rm -rf "$BOLTS_BUILD" 91 | mkdir -p "$BOLTS_BUILD" \ 92 | || die "Could not create directory $BOLTS_BUILD" 93 | 94 | test -d "$BOLTS_IOS_BUILD" \ 95 | || mkdir -p "$BOLTS_IOS_BUILD" \ 96 | || die "Could not create directory $BOLTS_IOS_BUILD" 97 | 98 | test -d "$BOLTS_MACOS_BUILD" \ 99 | || mkdir -p "$BOLTS_MACOS_BUILD" \ 100 | || die "Could not create directory $BOLTS_MACOS_BUILD" 101 | 102 | if [ $WATCHOS -eq 1 ]; then 103 | test -d "$BOLTS_WATCHOS_BUILD" \ 104 | || mkdir -p "$BOLTS_WATCHOS_BUILD" \ 105 | || die "Could not create directory $BOLTS_WATCHOS_BUILD" 106 | fi 107 | 108 | if [ $TVOS -eq 1 ]; then 109 | test -d "$BOLTS_TVOS_BUILD" \ 110 | || mkdir -p "$BOLTS_TVOS_BUILD" \ 111 | || die "Could not create directory $BOLTS_TVOS_BUILD" 112 | fi 113 | 114 | cd "$BOLTS_SRC" 115 | function xcode_build_target() { 116 | echo "Compiling for platform: ${1}." 117 | "$XCODEBUILD" \ 118 | -target "${3}" \ 119 | -sdk $1 \ 120 | -configuration "${2}" \ 121 | SYMROOT="$BOLTS_BUILD" \ 122 | CURRENT_PROJECT_VERSION="$BOLTS_VERSION_FULL" \ 123 | build \ 124 | || die "Xcode build failed for platform: ${1}." 125 | } 126 | 127 | xcode_build_target "iphonesimulator" "${BUILDCONFIGURATION}" "Bolts-iOS" 128 | xcode_build_target "iphoneos" "${BUILDCONFIGURATION}" "Bolts-iOS" 129 | xcode_build_target "macosx" "${BUILDCONFIGURATION}" "Bolts-macOS" 130 | if [ $WATCHOS -eq 1 ]; then 131 | xcode_build_target "watchsimulator" "${BUILDCONFIGURATION}" "Bolts-watchOS" 132 | xcode_build_target "watchos" "${BUILDCONFIGURATION}" "Bolts-watchOS" 133 | fi 134 | if [ $TVOS -eq 1 ]; then 135 | xcode_build_target "appletvsimulator" "${BUILDCONFIGURATION}" "Bolts-tvOS" 136 | xcode_build_target "appletvos" "${BUILDCONFIGURATION}" "Bolts-tvOS" 137 | fi 138 | 139 | # ----------------------------------------------------------------------------- 140 | # Merge lib files for different platforms into universal binary 141 | # 142 | progress_message "Building Bolts univeral library using lipo." 143 | 144 | mkdir -p "$(dirname "$BOLTS_IOS_BINARY")" 145 | 146 | if [ $WATCHOS -eq 1 ]; then 147 | mkdir -p "$(dirname "$BOLTS_WATCHOS_BINARY")" 148 | fi 149 | 150 | if [ $TVOS -eq 1 ]; then 151 | mkdir -p "$(dirname "$BOLTS_TVOS_BINARY")" 152 | fi 153 | 154 | # Copy/Paste iOS Framework to get structure/resources/etc 155 | cp -av \ 156 | "$BOLTS_BUILD/${BUILDCONFIGURATION}-iphoneos/Bolts.framework" \ 157 | "$BOLTS_BUILD/${BUILDCONFIGURATION}-universal" 158 | rm "$BOLTS_BUILD/${BUILDCONFIGURATION}-universal/Bolts.framework/Bolts" 159 | 160 | if [ $WATCHOS -eq 1 ]; then 161 | # Copy/Paste watchOS framework to get structure/resources/etc 162 | cp -av \ 163 | "$BOLTS_BUILD/${BUILDCONFIGURATION}-watchos/Bolts.framework" \ 164 | "$BOLTS_BUILD/${BUILDCONFIGURATION}-watch-universal" 165 | rm "$BOLTS_BUILD/${BUILDCONFIGURATION}-watch-universal/Bolts.framework/Bolts" 166 | fi 167 | 168 | if [ $TVOS -eq 1 ]; then 169 | # Copy/Paste tvOS framework to get structure/resources/etc 170 | cp -av \ 171 | "$BOLTS_BUILD/${BUILDCONFIGURATION}-appletvos/Bolts.framework" \ 172 | "$BOLTS_BUILD/${BUILDCONFIGURATION}-appletv-universal" 173 | rm "$BOLTS_BUILD/${BUILDCONFIGURATION}-appletv-universal/Bolts.framework/Bolts" 174 | fi 175 | 176 | # Combine iOS/Simulator binaries into a single universal binary. 177 | "$LIPO" \ 178 | -create \ 179 | "$BOLTS_BUILD/${BUILDCONFIGURATION}-iphonesimulator/Bolts.framework/Bolts" \ 180 | "$BOLTS_BUILD/${BUILDCONFIGURATION}-iphoneos/Bolts.framework/Bolts" \ 181 | -output "$BOLTS_IOS_BINARY" \ 182 | || die "lipo failed - could not create universal static library" 183 | 184 | if [ $WATCHOS -eq 1 ]; then 185 | # Combine watchOS/Simulator binaries into a single universal binary. 186 | "$LIPO" \ 187 | -create \ 188 | "$BOLTS_BUILD/${BUILDCONFIGURATION}-watchsimulator/Bolts.framework/Bolts" \ 189 | "$BOLTS_BUILD/${BUILDCONFIGURATION}-watchos/Bolts.framework/Bolts" \ 190 | -output "$BOLTS_WATCHOS_BINARY" \ 191 | || die "lipo failed - could not create universal static library" 192 | fi 193 | 194 | if [ $TVOS -eq 1 ]; then 195 | # Combine tvOS/Simulator binaries into a single universal binary. 196 | "$LIPO" \ 197 | -create \ 198 | "$BOLTS_BUILD/${BUILDCONFIGURATION}-appletvsimulator/Bolts.framework/Bolts" \ 199 | "$BOLTS_BUILD/${BUILDCONFIGURATION}-appletvos/Bolts.framework/Bolts" \ 200 | -output "$BOLTS_TVOS_BINARY" \ 201 | || die "lipo failed - could not create universal static library" 202 | fi 203 | 204 | # Copy/Paste created iOS Framework to final location 205 | cp -av "$(dirname "$BOLTS_IOS_BINARY")" $BOLTS_IOS_FRAMEWORK 206 | 207 | # Copy/Paste macOS framework, as this is already built for us 208 | cp -av "$BOLTS_MACOS_BINARY" $BOLTS_MACOS_FRAMEWORK 209 | 210 | if [ $WATCHOS -eq 1 ]; then 211 | # Copy/Paste watchOS Framework 212 | cp -av "$(dirname "$BOLTS_WATCHOS_BINARY")" $BOLTS_WATCHOS_FRAMEWORK 213 | fi 214 | 215 | if [ $TVOS -eq 1 ]; then 216 | # Copy/Paste tvOS Framework 217 | cp -av "$(dirname "$BOLTS_TVOS_BINARY")" $BOLTS_TVOS_FRAMEWORK 218 | fi 219 | 220 | # ----------------------------------------------------------------------------- 221 | # Run unit tests 222 | # 223 | 224 | if [ ${NOEXTRAS:-0} -eq 1 ];then 225 | progress_message "Skipping unit tests." 226 | else 227 | progress_message "Running unit tests." 228 | cd $BOLTS_SRC 229 | $BOLTS_SCRIPT/run_tests.sh -c $BUILDCONFIGURATION MacBolts 230 | fi 231 | 232 | # ----------------------------------------------------------------------------- 233 | # Done 234 | # 235 | 236 | common_success 237 | -------------------------------------------------------------------------------- /scripts/build_release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright 2010-present Facebook. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | # This script builds Bolts.framework and includes it in the zip file ready for distribution 19 | 20 | . ${BOLTS_SCRIPT:-$(dirname $0)}/common.sh 21 | 22 | # ----------------------------------------------------------------------------- 23 | # Call out to build .framework 24 | # 25 | . $BOLTS_SCRIPT/build_framework.sh --with-watchos --with-tvos -c Release 26 | 27 | cd $BOLTS_BUILD 28 | zip -r --symlinks $BOLTS_DISTRIBUTION_ARCHIVE ios/ osx/ watchOS/ tvOS/ 29 | 30 | common_success 31 | -------------------------------------------------------------------------------- /scripts/common.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright 2010-present Facebook. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | # This script sets up a consistent environment for the other scripts in this directory. 19 | 20 | # Set up paths for a specific clone of the SDK source 21 | if [ -z "$BOLTS_SCRIPT" ]; then 22 | # --------------------------------------------------------------------------- 23 | # Versioning for the SDK 24 | # 25 | BOLTS_VERSION_MAJOR=0 26 | BOLTS_VERSION_MINOR=1 27 | test -n "$BOLTS_VERSION_BUILD" || BOLTS_VERSION_BUILD=$(date '+%Y%m%d') 28 | BOLTS_VERSION=${BOLTS_VERSION_MAJOR}.${BOLTS_VERSION_MINOR} 29 | BOLTS_VERSION_FULL=${BOLTS_VERSION}.${BOLTS_VERSION_BUILD} 30 | 31 | # --------------------------------------------------------------------------- 32 | # Set up paths 33 | # 34 | 35 | # The directory containing this script 36 | # We need to go there and use pwd so these are all absolute paths 37 | pushd "$(dirname $BASH_SOURCE[0])" >/dev/null 38 | BOLTS_SCRIPT="$(pwd)" 39 | popd >/dev/null 40 | 41 | # The root directory where Bolts is cloned 42 | BOLTS_ROOT=$(dirname "$BOLTS_SCRIPT") 43 | 44 | # Path to source files for Bolts 45 | BOLTS_SRC=$BOLTS_ROOT 46 | 47 | # The directory where the target is built 48 | BOLTS_BUILD=$BOLTS_ROOT/build 49 | BOLTS_IOS_BUILD=$BOLTS_ROOT/build/ios 50 | BOLTS_MACOS_BUILD=$BOLTS_ROOT/build/osx 51 | BOLTS_WATCHOS_BUILD=$BOLTS_ROOT/build/watchOS 52 | BOLTS_TVOS_BUILD=$BOLTS_ROOT/build/tvOS 53 | BOLTS_BUILD_LOG=$BOLTS_BUILD/build.log 54 | 55 | # The name of the Bolts framework 56 | BOLTS_FRAMEWORK_NAME=Bolts.framework 57 | 58 | # The path to the built Bolts .framework file 59 | BOLTS_IOS_FRAMEWORK=$BOLTS_IOS_BUILD/$BOLTS_FRAMEWORK_NAME 60 | BOLTS_MACOS_FRAMEWORK=$BOLTS_MACOS_BUILD/$BOLTS_FRAMEWORK_NAME 61 | BOLTS_WATCHOS_FRAMEWORK=$BOLTS_WATCHOS_BUILD/$BOLTS_FRAMEWORK_NAME 62 | BOLTS_TVOS_FRAMEWORK=$BOLTS_TVOS_BUILD/$BOLTS_FRAMEWORK_NAME 63 | 64 | # The name of the docset 65 | BOLTS_DOCSET_NAME=Bolts.docset 66 | 67 | # The path to the framework docs 68 | BOLTS_FRAMEWORK_DOCS=$BOLTS_BUILD/$BOLTS_DOCSET_NAME 69 | 70 | # Archive name for distribution 71 | BOLTS_DISTRIBUTION_ARCHIVE=Bolts-iOS.zip 72 | 73 | fi 74 | 75 | # Set up one-time variables 76 | if [ -z $BOLTS_ENV ]; then 77 | BOLTS_ENV=env1 78 | BOLTS_BUILD_DEPTH=0 79 | 80 | # Explains where the log is if this is the outermost build or if 81 | # we hit a fatal error. 82 | function show_summary() { 83 | test -r "$BOLTS_BUILD_LOG" && echo "Build log is at $BOLTS_BUILD_LOG" 84 | } 85 | 86 | # Determines whether this is out the outermost build. 87 | function is_outermost_build() { 88 | test 1 -eq $BOLTS_BUILD_DEPTH 89 | } 90 | 91 | # Calls show_summary if this is the outermost build. 92 | # Do not call outside common.sh. 93 | function pop_common() { 94 | BOLTS_BUILD_DEPTH=$(($BOLTS_BUILD_DEPTH - 1)) 95 | test 0 -eq $BOLTS_BUILD_DEPTH && show_summary 96 | } 97 | 98 | # Deletes any previous build log if this is the outermost build. 99 | # Do not call outside common.sh. 100 | function push_common() { 101 | test 0 -eq $BOLTS_BUILD_DEPTH && \rm -f $BOLTS_BUILD_LOG 102 | BOLTS_BUILD_DEPTH=$(($BOLTS_BUILD_DEPTH + 1)) 103 | } 104 | 105 | # Echoes a progress message to stderr 106 | function progress_message() { 107 | echo "$@" >&2 108 | } 109 | 110 | # Any script that includes common.sh must call this once if it finishes 111 | # successfully. 112 | function common_success() { 113 | pop_common 114 | return 0 115 | } 116 | 117 | # Call this when there is an error. This does not return. 118 | function die() { 119 | echo "" 120 | echo "FATAL: $*" >&2 121 | show_summary 122 | exit 1 123 | } 124 | 125 | test -n "$XCODEBUILD" || XCODEBUILD=$(which xcodebuild) 126 | test -n "$LIPO" || LIPO=$(which lipo) 127 | test -n "$PACKAGEMAKER" || PACKAGEMAKER=$(which PackageMaker) 128 | test -n "$CODESIGN" || CODESIGN=$(which codesign) 129 | test -n "$APPLEDOC" || APPLEDOC=$(which appledoc) 130 | 131 | # < XCode 4.3.1 132 | if [ ! -x "$XCODEBUILD" ]; then 133 | # XCode from app store 134 | XCODEBUILD=/Applications/XCode.app/Contents/Developer/usr/bin/xcodebuild 135 | fi 136 | 137 | if [ ! -x "$PACKAGEMAKER" ]; then 138 | PACKAGEMAKER=/Developer/Applications/Utilities/PackageMaker.app/Contents/MacOS/PackageMaker 139 | fi 140 | 141 | if [ ! -x "$PACKAGEMAKER" ]; then 142 | PACKAGEMAKER=/Applications/PackageMaker.app/Contents/MacOS/PackageMaker 143 | fi 144 | fi 145 | 146 | # Increment depth every time we . this file. At the end of any script 147 | # that .'s this file, there should be a call to common_success to decrement. 148 | push_common 149 | --------------------------------------------------------------------------------