Skip to content

Commit 95cec59

Browse files
committed
'Cancel' for PromiseKit -- provides the ability to cancel promises and promise chains
1 parent 394436a commit 95cec59

16 files changed

+645
-57
lines changed

.travis.yml

Lines changed: 64 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,64 @@
11
matrix:
22
include:
3-
- {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=iOS DST="OS=10.3.1,name=iPhone SE"', os: osx, language: objective-c}
4-
- {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=tvOS DST="OS=10.2,name=Apple TV 1080p"', os: osx, language: objective-c}
5-
- {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c}
6-
- {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=watchOS DST="OS=3.2,name=Apple Watch - 38mm"', os: osx, language: objective-c}
3+
- {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=iOS DST="OS=10.3.1,name=iPhone SE"', os: osx, language: objective-c}
4+
- {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=tvOS DST="OS=10.2,name=Apple TV 1080p"', os: osx, language: objective-c}
5+
- {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c}
6+
- {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=watchOS DST="OS=3.2,name=Apple Watch - 38mm"', os: osx, language: objective-c}
77

8-
- {osx_image: xcode9.2, env: 'SWFT=3.2 PLAT=iOS DST="OS=11.2,name=iPhone SE"', os: osx, language: objective-c}
9-
- {osx_image: xcode9.2, env: 'SWFT=3.2 PLAT=tvOS DST="OS=11.2,name=Apple TV"', os: osx, language: objective-c}
10-
- {osx_image: xcode9.2, env: 'SWFT=3.2 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c}
11-
- {osx_image: xcode9.2, env: 'SWFT=3.2 PLAT=watchOS DST="OS=4.2,name=Apple Watch - 38mm"', os: osx, language: objective-c}
8+
- {osx_image: xcode9.2, env: 'SWFT=3.2 PLAT=iOS DST="OS=11.2,name=iPhone SE"', os: osx, language: objective-c}
9+
- {osx_image: xcode9.2, env: 'SWFT=3.2 PLAT=tvOS DST="OS=11.2,name=Apple TV"', os: osx, language: objective-c}
10+
- {osx_image: xcode9.2, env: 'SWFT=3.2 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c}
11+
- {osx_image: xcode9.2, env: 'SWFT=3.2 PLAT=watchOS DST="OS=4.2,name=Apple Watch - 38mm"', os: osx, language: objective-c}
1212

13-
- {osx_image: xcode9.3, env: 'SWFT=3.3 PLAT=iOS DST="OS=11.3,name=iPhone SE"', os: osx, language: objective-c}
14-
- {osx_image: xcode9.3, env: 'SWFT=3.3 PLAT=tvOS DST="OS=11.3,name=Apple TV"', os: osx, language: objective-c}
15-
- {osx_image: xcode9.3, env: 'SWFT=3.3 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c}
16-
- {osx_image: xcode9.3, env: 'SWFT=3.3 PLAT=watchOS DST="OS=4.3,name=Apple Watch - 38mm"', os: osx, language: objective-c}
13+
- {osx_image: xcode9.4, env: 'SWFT=3.3 PLAT=iOS DST="OS=11.4,name=iPhone 5s"', os: osx, language: objective-c}
14+
- {osx_image: xcode9.4, env: 'SWFT=3.3 PLAT=tvOS DST="OS=11.4,name=Apple TV"', os: osx, language: objective-c}
15+
- {osx_image: xcode9.3, env: 'SWFT=3.3 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c}
16+
- {osx_image: xcode9.3, env: 'SWFT=3.3 PLAT=watchOS DST="OS=4.3,name=Apple Watch - 38mm"', os: osx, language: objective-c}
1717

18-
- {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c}
19-
- {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=iOS DST="OS=8.4,name=iPhone 4s"', os: osx, language: objective-c}
20-
- {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=iOS DST="OS=9.3,name=iPhone SE"', os: osx, language: objective-c}
21-
- {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=iOS DST="OS=10.3.1,name=iPhone SE"', os: osx, language: objective-c}
22-
- {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=iOS DST="OS=11.2,name=iPhone SE"', os: osx, language: objective-c}
23-
- {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=tvOS DST="OS=9.2,name=Apple TV 1080p"', os: osx, language: objective-c}
24-
- {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=tvOS DST="OS=10.2,name=Apple TV 1080p"', os: osx, language: objective-c}
25-
- {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=tvOS DST="OS=11.2,name=Apple TV"', os: osx, language: objective-c}
26-
- {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=watchOS DST="OS=2.2,name=Apple Watch - 38mm"', os: osx, language: objective-c}
27-
- {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=watchOS DST="OS=3.2,name=Apple Watch - 38mm"', os: osx, language: objective-c}
28-
- {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=watchOS DST="OS=4.2,name=Apple Watch - 38mm"', os: osx, language: objective-c}
18+
- {osx_image: xcode10, env: 'SWFT=3.4 PLAT=iOS DST="OS=12.0,name=iPhone SE"', os: osx, language: objective-c}
19+
- {osx_image: xcode10, env: 'SWFT=3.4 PLAT=tvOS DST="OS=12.0,name=Apple TV"', os: osx, language: objective-c}
20+
- {osx_image: xcode10, env: 'SWFT=3.4 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c}
21+
- {osx_image: xcode10, env: 'SWFT=3.4 PLAT=watchOS DST="OS=5.0,name=Apple Watch Series 3 - 42mm"', os: osx, language: objective-c}
2922

30-
- {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=iOS DST="OS=11.3,name=iPhone SE"', os: osx, language: objective-c}
31-
- {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=tvOS DST="OS=11.3,name=Apple TV"', os: osx, language: objective-c}
32-
- {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c}
33-
- {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=watchOS DST="OS=4.3,name=Apple Watch - 38mm"', os: osx, language: objective-c}
23+
- {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=iOS DST="OS=11.2,name=iPhone SE"', os: osx, language: objective-c}
24+
- {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=tvOS DST="OS=11.2,name=Apple TV"', os: osx, language: objective-c}
25+
- {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c}
26+
- {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=watchOS DST="OS=4.2,name=Apple Watch - 38mm"', os: osx, language: objective-c}
27+
28+
- {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=macOS DST="arch=x86_64" TEST=1', os: osx, language: objective-c}
29+
- {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=iOS DST="OS=8.4,name=iPhone 4s"', os: osx, language: objective-c}
30+
- {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=iOS DST="OS=9.3,name=iPhone 5s"', os: osx, language: objective-c}
31+
- {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=iOS DST="OS=10.3.1,name=iPhone SE"', os: osx, language: objective-c}
32+
- {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=iOS DST="OS=11.4,name=iPhone 5s" TEST=1', os: osx, language: objective-c}
33+
- {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=tvOS DST="OS=9.2,name=Apple TV 1080p"', os: osx, language: objective-c}
34+
- {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=tvOS DST="OS=10.2,name=Apple TV 1080p"', os: osx, language: objective-c}
35+
- {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=tvOS DST="OS=11.4,name=Apple TV" TEST=1', os: osx, language: objective-c}
36+
- {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=watchOS DST="OS=2.2,name=Apple Watch - 38mm"', os: osx, language: objective-c}
37+
- {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=watchOS DST="OS=3.2,name=Apple Watch - 38mm"', os: osx, language: objective-c}
38+
- {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=watchOS DST="OS=4.3,name=Apple Watch - 38mm"', os: osx, language: objective-c}
39+
40+
- {osx_image: xcode10, env: 'SWFT=4.3 PLAT=iOS DST="OS=12.0,name=iPhone SE"', os: osx, language: objective-c}
41+
- {osx_image: xcode10, env: 'SWFT=4.3 PLAT=tvOS DST="OS=12.0,name=Apple TV"', os: osx, language: objective-c}
42+
- {osx_image: xcode10, env: 'SWFT=4.3 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c}
43+
- {osx_image: xcode10, env: 'SWFT=4.3 PLAT=watchOS DST="OS=5.0,name=Apple Watch Series 3 - 42mm"', os: osx, language: objective-c}
44+
45+
# Swift 3.2.0 (we have some source-conditionals for this version)
46+
- {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=3 SWIFT_VERSION=4.0'}
47+
# Swift 3.2.3
48+
- {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=3 SWIFT_VERSION=4.0.3'}
49+
# Swift 3.3
50+
- {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=3 SWIFT_VERSION=4.1.2 TEST=1'}
51+
# Swift 3.4
52+
- {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=3 SWIFT_VERSION=DEVELOPMENT-SNAPSHOT-2018-06-20-a TEST=1'}
53+
# Swift 4.0.0 (we have some source-conditionals for this version)
54+
- {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=4 SWIFT_VERSION=4.0'}
55+
# Swift 4.0.3
56+
- {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=4 SWIFT_VERSION=4.0.3'}
57+
# Swift 4.1
58+
- {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=4 SWIFT_VERSION=4.1.2 TEST=1'}
59+
# Swift 4.2
60+
- {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=4 SWIFT_VERSION=DEVELOPMENT-SNAPSHOT-2018-06-20-a TEST=1'}
3461

35-
- {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_VERSION=3.1'}
36-
- {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_VERSION=4.0'}
3762
cache:
3863
directories:
3964
- Carthage
@@ -42,22 +67,24 @@ before_install:
4267
carthage bootstrap --cache-builds --no-use-binaries --platform $PLAT --verbose;
4368
else
4469
eval "$(curl -sL https://swiftenv.fuller.li/install.sh)";
70+
swift --version;
4571
fi
4672
install:
4773
- case $PLAT in
48-
macOS|tvOS|iOS)
49-
xcodebuild -scheme PMKFoundation -quiet -destination "$DST" SWIFT_VERSION=$SWFT build-for-testing -enableCodeCoverage YES;;
50-
watchOS)
51-
xcodebuild -scheme PMKFoundation -quiet -destination "$DST" SWIFT_VERSION=$SWFT build;;
74+
macOS|tvOS|iOS|watchOS)
75+
xcodebuild -scheme PMKFoundation -target PMKFoundation -quiet -destination "$DST" SWIFT_VERSION=$SWFT SWIFT_TREAT_WARNINGS_AS_ERRORS=YES build;
76+
if [[ $TEST == "1" ]]; then
77+
xcodebuild -scheme PMKFoundation -target PMKNSTests -quiet -destination "$DST" SWIFT_TREAT_WARNINGS_AS_ERRORS=YES build;
78+
fi;;
5279
*)
53-
swift build;;
80+
swift build -Xswiftc -swift-version -Xswiftc $SWIFT_BUILD_VERSION;;
5481
esac
5582
script:
5683
- case $PLAT in
5784
macOS|tvOS|iOS)
58-
xcodebuild -scheme PMKFoundation -quiet -destination "$DST" test -enableCodeCoverage YES;;
59-
watchOS)
60-
;;
85+
if [[ $TEST == "1" ]]; then
86+
xcodebuild -scheme PMKFoundation -destination "$DST" test -enableCodeCoverage YES;
87+
fi;;
6188
*)
6289
;;
6390
esac

Cartfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
github "mxcl/PromiseKit" ~> 6.0
1+
#github "mxcl/PromiseKit" ~> 6.3
2+
github "dougzilla32/PromiseKit" "CoreCancel"

Cartfile.resolved

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
github "AliSoftware/OHHTTPStubs" "6.1.0"
2-
github "mxcl/PromiseKit" "6.1.0"
2+
github "dougzilla32/PromiseKit" "087b3cf470890ff9ea841212e2f3e285fecf3988"

PMKFoundation.xcodeproj/project.pbxproj

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@
337337
GCC_WARN_UNUSED_VARIABLE = YES;
338338
INFOPLIST_FILE = Info.plist;
339339
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
340-
MACOSX_DEPLOYMENT_TARGET = 10.9;
340+
MACOSX_DEPLOYMENT_TARGET = 10.10;
341341
MTL_ENABLE_DEBUG_INFO = YES;
342342
ONLY_ACTIVE_ARCH = YES;
343343
PRODUCT_BUNDLE_IDENTIFIER = org.promisekit.Foundation;
@@ -397,11 +397,10 @@
397397
GCC_WARN_UNUSED_VARIABLE = YES;
398398
INFOPLIST_FILE = Info.plist;
399399
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
400-
MACOSX_DEPLOYMENT_TARGET = 10.9;
400+
MACOSX_DEPLOYMENT_TARGET = 10.10;
401401
MTL_ENABLE_DEBUG_INFO = NO;
402402
PRODUCT_BUNDLE_IDENTIFIER = org.promisekit.Foundation;
403403
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos watchsimulator watchos macosx appletvsimulator appletvos";
404-
SWIFT_COMPILATION_MODE = wholemodule;
405404
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
406405
SWIFT_VERSION = 3.0;
407406
TARGETED_DEVICE_FAMILY = "1,2,3,4";
@@ -454,6 +453,7 @@
454453
PRODUCT_NAME = "$(TARGET_NAME)";
455454
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
456455
SWIFT_SUPPRESS_WARNINGS = YES;
456+
SWIFT_VERSION = 4.0;
457457
};
458458
name = Debug;
459459
};
@@ -466,6 +466,7 @@
466466
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
467467
PRODUCT_NAME = "$(TARGET_NAME)";
468468
SWIFT_SUPPRESS_WARNINGS = YES;
469+
SWIFT_VERSION = 4.0;
469470
};
470471
name = Release;
471472
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>IDEDidComputeMac32BitWarning</key>
6+
<true/>
7+
</dict>
8+
</plist>

PMKFoundation.xcodeproj/xcshareddata/xcschemes/PMKFoundation.xcscheme

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
version = "1.3">
55
<BuildAction
66
parallelizeBuildables = "YES"
7-
buildImplicitDependencies = "YES">
7+
buildImplicitDependencies = "NO">
88
<BuildActionEntries>
99
<BuildActionEntry
10-
buildForTesting = "YES"
10+
buildForTesting = "NO"
1111
buildForRunning = "YES"
1212
buildForProfiling = "YES"
1313
buildForArchiving = "YES"

Package.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ import PackageDescription
33
let package = Package(
44
name: "PMKFoundation",
55
dependencies: [
6-
.Package(url: "https://github.com/mxcl/PromiseKit.git", majorVersion: 6)
6+
// Switch this back before integrating:
7+
// .Package(url: "https://github.com/mxcl/PromiseKit.git", majorVersion: 6)
8+
.Package(url: "https://github.com/dougzilla32/PromiseKitCoreCancel.git", majorVersion: 6)
79
],
10+
swiftLanguageVersions: [3, 4],
811
exclude: [
912
"Sources/NSNotificationCenter+AnyPromise.m",
1013
"Sources/NSTask+AnyPromise.m",

Sources/NSNotificationCenter+Promise.swift

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,37 @@ extension NotificationCenter {
2222
/// Observe the named notification once
2323
public func observe(once name: Notification.Name, object: Any? = nil) -> Guarantee<Notification> {
2424
let (promise, fulfill) = Guarantee<Notification>.pending()
25-
#if !os(Linux)
26-
let id = addObserver(forName: name, object: object, queue: nil, using: fulfill)
27-
#else
25+
#if os(Linux) && ((swift(>=4.0) && !swift(>=4.0.1)) || (swift(>=3.0) && !swift(>=3.2.1)))
2826
let id = addObserver(forName: name, object: object, queue: nil, usingBlock: fulfill)
27+
#else
28+
let id = addObserver(forName: name, object: object, queue: nil, using: fulfill)
2929
#endif
30+
promise.setCancellableTask(ObserverTask { self.removeObserver(id) })
3031
promise.done { _ in self.removeObserver(id) }
3132
return promise
3233
}
3334
}
35+
36+
class ObserverTask: CancellableTask {
37+
let cancelBlock: () -> Void
38+
39+
init(cancelBlock: @escaping () -> Void) {
40+
self.cancelBlock = cancelBlock
41+
}
42+
43+
func cancel() {
44+
cancelBlock()
45+
isCancelled = true
46+
}
47+
48+
var isCancelled = false
49+
}
50+
51+
//////////////////////////////////////////////////////////// Cancellable wrapper
52+
53+
extension NotificationCenter {
54+
/// Observe the named notification once
55+
public func cancellableObserve(once name: Notification.Name, object: Any? = nil) -> CancellablePromise<Notification> {
56+
return cancellable(observe(once: name, object: object))
57+
}
58+
}

Sources/NSObject+Promise.swift

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,19 @@ extension NSObject {
2929
}
3030
}
3131

32-
private class KVOProxy: NSObject {
32+
private class KVOProxy: NSObject, CancellableTask {
3333
var retainCycle: KVOProxy?
3434
let fulfill: (Any?) -> Void
35+
let observeeObject: NSObject
36+
let observeeKeyPath: String
37+
var observing: Bool
3538

3639
@discardableResult
3740
init(observee: NSObject, keyPath: String, resolve: @escaping (Any?) -> Void) {
3841
fulfill = resolve
42+
observeeObject = observee
43+
observeeKeyPath = keyPath
44+
observing = true
3945
super.init()
4046
observee.addObserver(self, forKeyPath: keyPath, options: NSKeyValueObservingOptions.new, context: pointer)
4147
retainCycle = self
@@ -47,11 +53,37 @@ private class KVOProxy: NSObject {
4753
fulfill(change[NSKeyValueChangeKey.newKey])
4854
if let object = object as? NSObject, let keyPath = keyPath {
4955
object.removeObserver(self, forKeyPath: keyPath)
56+
observing = false
5057
}
5158
}
5259
}
5360

61+
func cancel() {
62+
if !isCancelled {
63+
if observing {
64+
observeeObject.removeObserver(self, forKeyPath: observeeKeyPath)
65+
observing = false
66+
}
67+
isCancelled = true
68+
}
69+
}
70+
71+
var isCancelled = false
72+
5473
private lazy var pointer: UnsafeMutableRawPointer = {
5574
return Unmanaged<KVOProxy>.passUnretained(self).toOpaque()
5675
}()
5776
}
77+
78+
//////////////////////////////////////////////////////////// Cancellable wrapper
79+
80+
extension NSObject {
81+
/**
82+
- Returns: A promise that resolves when the provided keyPath changes, or when the promise is cancelled.
83+
- Warning: *Important* The promise must not outlive the object under observation.
84+
- SeeAlso: Apple’s KVO documentation.
85+
*/
86+
public func cancellableObserve(_: PMKNamespacer, keyPath: String) -> CancellablePromise<Any?> {
87+
return cancellable(observe(.promise, keyPath: keyPath))
88+
}
89+
}

0 commit comments

Comments
 (0)