Skip to content

Commit

Permalink
'Cancel' for PromiseKit -- provides the ability to cancel promises an…
Browse files Browse the repository at this point in the history
…d promise chains
  • Loading branch information
dougzilla32 committed Sep 12, 2018
1 parent 7cbd050 commit bfcb3f7
Show file tree
Hide file tree
Showing 15 changed files with 635 additions and 54 deletions.
101 changes: 64 additions & 37 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,39 +1,64 @@
matrix:
include:
- {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=iOS DST="OS=10.3.1,name=iPhone SE"', os: osx, language: objective-c}
- {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=tvOS DST="OS=10.2,name=Apple TV 1080p"', os: osx, language: objective-c}
- {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c}
- {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=watchOS DST="OS=3.2,name=Apple Watch - 38mm"', os: osx, language: objective-c}
- {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=iOS DST="OS=10.3.1,name=iPhone SE"', os: osx, language: objective-c}
- {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=tvOS DST="OS=10.2,name=Apple TV 1080p"', os: osx, language: objective-c}
- {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c}
- {osx_image: xcode8.3, env: 'SWFT=3.1 PLAT=watchOS DST="OS=3.2,name=Apple Watch - 38mm"', os: osx, language: objective-c}

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

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

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

- {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=iOS DST="OS=11.3,name=iPhone SE"', os: osx, language: objective-c}
- {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=tvOS DST="OS=11.3,name=Apple TV"', os: osx, language: objective-c}
- {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c}
- {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=watchOS DST="OS=4.3,name=Apple Watch - 38mm"', os: osx, language: objective-c}
- {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=iOS DST="OS=11.2,name=iPhone SE"', os: osx, language: objective-c}
- {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=tvOS DST="OS=11.2,name=Apple TV"', os: osx, language: objective-c}
- {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c}
- {osx_image: xcode9.2, env: 'SWFT=4.0 PLAT=watchOS DST="OS=4.2,name=Apple Watch - 38mm"', os: osx, language: objective-c}

- {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=macOS DST="arch=x86_64" TEST=1', os: osx, language: objective-c}
- {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=iOS DST="OS=8.4,name=iPhone 4s"', os: osx, language: objective-c}
- {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=iOS DST="OS=9.3,name=iPhone 5s"', os: osx, language: objective-c}
- {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=iOS DST="OS=10.3.1,name=iPhone SE"', os: osx, language: objective-c}
- {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=iOS DST="OS=11.4,name=iPhone 5s" TEST=1', os: osx, language: objective-c}
- {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=tvOS DST="OS=9.2,name=Apple TV 1080p"', os: osx, language: objective-c}
- {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=tvOS DST="OS=10.2,name=Apple TV 1080p"', os: osx, language: objective-c}
- {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=tvOS DST="OS=11.4,name=Apple TV" TEST=1', os: osx, language: objective-c}
- {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=watchOS DST="OS=2.2,name=Apple Watch - 38mm"', os: osx, language: objective-c}
- {osx_image: xcode9.3, env: 'SWFT=4.1 PLAT=watchOS DST="OS=3.2,name=Apple Watch - 38mm"', os: osx, language: objective-c}
- {osx_image: xcode9.4, env: 'SWFT=4.1 PLAT=watchOS DST="OS=4.3,name=Apple Watch - 38mm"', os: osx, language: objective-c}

- {osx_image: xcode10, env: 'SWFT=4.3 PLAT=iOS DST="OS=12.0,name=iPhone SE"', os: osx, language: objective-c}
- {osx_image: xcode10, env: 'SWFT=4.3 PLAT=tvOS DST="OS=12.0,name=Apple TV"', os: osx, language: objective-c}
- {osx_image: xcode10, env: 'SWFT=4.3 PLAT=macOS DST="arch=x86_64"', os: osx, language: objective-c}
- {osx_image: xcode10, env: 'SWFT=4.3 PLAT=watchOS DST="OS=5.0,name=Apple Watch Series 3 - 42mm"', os: osx, language: objective-c}

# Swift 3.2.0 (we have some source-conditionals for this version)
- {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=3 SWIFT_VERSION=4.0'}
# Swift 3.2.3
- {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=3 SWIFT_VERSION=4.0.3'}
# Swift 3.3
- {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=3 SWIFT_VERSION=4.1.2 TEST=1'}
# Swift 3.4
- {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=3 SWIFT_VERSION=DEVELOPMENT-SNAPSHOT-2018-06-20-a TEST=1'}
# Swift 4.0.0 (we have some source-conditionals for this version)
- {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=4 SWIFT_VERSION=4.0'}
# Swift 4.0.3
- {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=4 SWIFT_VERSION=4.0.3'}
# Swift 4.1
- {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=4 SWIFT_VERSION=4.1.2 TEST=1'}
# Swift 4.2
- {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_BUILD_VERSION=4 SWIFT_VERSION=DEVELOPMENT-SNAPSHOT-2018-06-20-a TEST=1'}

- {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_VERSION=3.1'}
- {os: linux, dist: trusty, sudo: required, language: generic, env: 'SWIFT_VERSION=4.0'}
cache:
directories:
- Carthage
Expand All @@ -42,22 +67,24 @@ before_install:
carthage bootstrap --cache-builds --no-use-binaries --platform $PLAT --verbose;
else
eval "$(curl -sL https://swiftenv.fuller.li/install.sh)";
swift --version;
fi
install:
- case $PLAT in
macOS|tvOS|iOS)
xcodebuild -scheme PMKFoundation -quiet -destination "$DST" SWIFT_VERSION=$SWFT build-for-testing -enableCodeCoverage YES;;
watchOS)
xcodebuild -scheme PMKFoundation -quiet -destination "$DST" SWIFT_VERSION=$SWFT build;;
macOS|tvOS|iOS|watchOS)
xcodebuild -scheme PMKFoundation -target PMKFoundation -quiet -destination "$DST" SWIFT_VERSION=$SWFT SWIFT_TREAT_WARNINGS_AS_ERRORS=YES build;
if [[ $TEST == "1" ]]; then
xcodebuild -scheme PMKFoundation -target PMKNSTests -quiet -destination "$DST" SWIFT_TREAT_WARNINGS_AS_ERRORS=YES build;
fi;;
*)
swift build;;
swift build -Xswiftc -swift-version -Xswiftc $SWIFT_BUILD_VERSION;;
esac
script:
- case $PLAT in
macOS|tvOS|iOS)
xcodebuild -scheme PMKFoundation -quiet -destination "$DST" test -enableCodeCoverage YES;;
watchOS)
;;
if [[ $TEST == "1" ]]; then
xcodebuild -scheme PMKFoundation -destination "$DST" test -enableCodeCoverage YES;
fi;;
*)
;;
esac
Expand Down
3 changes: 2 additions & 1 deletion Cartfile
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
github "mxcl/PromiseKit" ~> 6.3
#github "mxcl/PromiseKit" ~> 6.3
github "dougzilla32/PromiseKit" "CoreCancel"
2 changes: 1 addition & 1 deletion Cartfile.resolved
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
github "AliSoftware/OHHTTPStubs" "6.1.0"
github "mxcl/PromiseKit" "6.3.0"
github "dougzilla32/PromiseKit" "ff694600d4d03458121515bdc027ba76df14f7ef"
2 changes: 2 additions & 0 deletions PMKFoundation.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_SUPPRESS_WARNINGS = YES;
SWIFT_VERSION = 4.0;
};
name = Debug;
};
Expand All @@ -465,6 +466,7 @@
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_SUPPRESS_WARNINGS = YES;
SWIFT_VERSION = 4.0;
};
name = Release;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
buildImplicitDependencies = "NO">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForTesting = "NO"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
Expand Down
5 changes: 4 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ import PackageDescription
let package = Package(
name: "PMKFoundation",
dependencies: [
.Package(url: "https://github.com/mxcl/PromiseKit.git", majorVersion: 6)
// Switch this back before integrating:
// .Package(url: "https://github.com/mxcl/PromiseKit.git", majorVersion: 6)
.Package(url: "https://github.com/dougzilla32/PromiseKitCoreCancel.git", majorVersion: 6)
],
swiftLanguageVersions: [3, 4],
exclude: [
"Sources/NSNotificationCenter+AnyPromise.m",
"Sources/NSTask+AnyPromise.m",
Expand Down
31 changes: 28 additions & 3 deletions Sources/NSNotificationCenter+Promise.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,37 @@ extension NotificationCenter {
/// Observe the named notification once
public func observe(once name: Notification.Name, object: Any? = nil) -> Guarantee<Notification> {
let (promise, fulfill) = Guarantee<Notification>.pending()
#if !os(Linux)
let id = addObserver(forName: name, object: object, queue: nil, using: fulfill)
#else
#if os(Linux) && ((swift(>=4.0) && !swift(>=4.0.1)) || (swift(>=3.0) && !swift(>=3.2.1)))
let id = addObserver(forName: name, object: object, queue: nil, usingBlock: fulfill)
#else
let id = addObserver(forName: name, object: object, queue: nil, using: fulfill)
#endif
promise.setCancellableTask(ObserverTask { self.removeObserver(id) })
promise.done { _ in self.removeObserver(id) }
return promise
}
}

class ObserverTask: CancellableTask {
let cancelBlock: () -> Void

init(cancelBlock: @escaping () -> Void) {
self.cancelBlock = cancelBlock
}

func cancel() {
cancelBlock()
isCancelled = true
}

var isCancelled = false
}

//////////////////////////////////////////////////////////// Cancellable wrapper

extension NotificationCenter {
/// Observe the named notification once
public func cancellableObserve(once name: Notification.Name, object: Any? = nil) -> CancellablePromise<Notification> {
return cancellable(observe(once: name, object: object))
}
}
34 changes: 33 additions & 1 deletion Sources/NSObject+Promise.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,19 @@ extension NSObject {
}
}

private class KVOProxy: NSObject {
private class KVOProxy: NSObject, CancellableTask {
var retainCycle: KVOProxy?
let fulfill: (Any?) -> Void
let observeeObject: NSObject
let observeeKeyPath: String
var observing: Bool

@discardableResult
init(observee: NSObject, keyPath: String, resolve: @escaping (Any?) -> Void) {
fulfill = resolve
observeeObject = observee
observeeKeyPath = keyPath
observing = true
super.init()
observee.addObserver(self, forKeyPath: keyPath, options: NSKeyValueObservingOptions.new, context: pointer)
retainCycle = self
Expand All @@ -47,11 +53,37 @@ private class KVOProxy: NSObject {
fulfill(change[NSKeyValueChangeKey.newKey])
if let object = object as? NSObject, let keyPath = keyPath {
object.removeObserver(self, forKeyPath: keyPath)
observing = false
}
}
}

func cancel() {
if !isCancelled {
if observing {
observeeObject.removeObserver(self, forKeyPath: observeeKeyPath)
observing = false
}
isCancelled = true
}
}

var isCancelled = false

private lazy var pointer: UnsafeMutableRawPointer = {
return Unmanaged<KVOProxy>.passUnretained(self).toOpaque()
}()
}

//////////////////////////////////////////////////////////// Cancellable wrapper

extension NSObject {
/**
- Returns: A promise that resolves when the provided keyPath changes, or when the promise is cancelled.
- Warning: *Important* The promise must not outlive the object under observation.
- SeeAlso: Apple’s KVO documentation.
*/
public func cancellableObserve(_: PMKNamespacer, keyPath: String) -> CancellablePromise<Any?> {
return cancellable(observe(.promise, keyPath: keyPath))
}
}
Loading

0 comments on commit bfcb3f7

Please sign in to comment.