├── .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 |
--------------------------------------------------------------------------------