From c6de0ed3575a9eef897cbd434d531cbd87bd4801 Mon Sep 17 00:00:00 2001 From: dougzilla32 Date: Sun, 7 Oct 2018 15:25:47 +0900 Subject: [PATCH 1/2] 'Cancel' for PromiseKit -- provides the ability to cancel promises and promise chains --- .travis.yml | 8 ++ Cartfile | 3 +- Cartfile.resolved | 2 +- Sources/MKDirections+Promise.swift | 108 +++++++++++++++++++------ Sources/MKMapSnapshotter+Promise.swift | 83 ++++++++++++++----- Tests/TestMapKit.swift | 67 +++++++++++++++ 6 files changed, 222 insertions(+), 49 deletions(-) diff --git a/.travis.yml b/.travis.yml index 98f3658..be33622 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,10 @@ matrix: - {osx_image: xcode9.4, env: 'PLAT=iOS SWFT=3.3 DST="OS=11.4,name=iPhone 5s"'} - {osx_image: xcode9.4, env: 'PLAT=tvOS SWFT=3.3 DST="OS=11.4,name=Apple TV"'} + - {osx_image: xcode10, env: 'PLAT=macOS SWFT=3.4 DST="arch=x86_64"'} + - {osx_image: xcode10, env: 'PLAT=iOS SWFT=3.4 DST="OS=12.0,name=iPhone SE"'} + - {osx_image: xcode10, env: 'PLAT=tvOS SWFT=3.4 DST="OS=12.0,name=Apple TV"'} + - {osx_image: xcode9.2, env: 'PLAT=macOS SWFT=4.0 DST="arch=x86_64"'} - {osx_image: xcode9.2, env: 'PLAT=iOS SWFT=4.0 DST="OS=11.2,name=iPhone SE"'} - {osx_image: xcode9.2, env: 'PLAT=tvOS SWFT=4.0 DST="OS=11.2,name=Apple TV"'} @@ -25,6 +29,10 @@ matrix: - {osx_image: xcode9.4, env: 'PLAT=iOS SWFT=4.1 DST="OS=11.4,name=iPhone 5s" TEST=1'} - {osx_image: xcode9.3, env: 'PLAT=tvOS SWFT=4.1 DST="OS=10.2,name=Apple TV 1080p"'} - {osx_image: xcode9.4, env: 'PLAT=tvOS SWFT=4.1 DST="OS=11.4,name=Apple TV" TEST=1'} + + - {osx_image: xcode10, env: 'PLAT=macOS SWFT=4.2 DST="arch=x86_64"'} + - {osx_image: xcode10, env: 'PLAT=iOS SWFT=4.2 DST="OS=12.0,name=iPhone SE"'} + - {osx_image: xcode10, env: 'PLAT=tvOS SWFT=4.2 DST="OS=12.0,name=Apple TV"'} cache: directories: - Carthage diff --git a/Cartfile b/Cartfile index 2bfea98..c517d21 100644 --- a/Cartfile +++ b/Cartfile @@ -1 +1,2 @@ -github "mxcl/PromiseKit" ~> 6.0 +#github "mxcl/PromiseKit" ~> 6.0 +github "dougzilla32/PromiseKit" "CoreCancel" diff --git a/Cartfile.resolved b/Cartfile.resolved index a1be206..80a4000 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1 +1 @@ -github "mxcl/PromiseKit" "6.3.3" +github "dougzilla32/PromiseKit" "087b3cf470890ff9ea841212e2f3e285fecf3988" diff --git a/Sources/MKDirections+Promise.swift b/Sources/MKDirections+Promise.swift index 04b3ff9..24b005a 100644 --- a/Sources/MKDirections+Promise.swift +++ b/Sources/MKDirections+Promise.swift @@ -1,26 +1,82 @@ -import MapKit -#if !PMKCocoaPods -import PromiseKit -#endif - -/** - To import the `MKDirections` category: - - use_frameworks! - pod "PromiseKit/MapKit" - - And then in your sources: - - import PromiseKit -*/ -extension MKDirections { - /// Begins calculating the requested route information asynchronously. - public func calculate() -> Promise { - return Promise { calculate(completionHandler: $0.resolve) } - } - - /// Begins calculating the requested travel-time information asynchronously. - public func calculateETA() -> Promise { - return Promise { calculateETA(completionHandler: $0.resolve) } - } -} +import MapKit +#if !PMKCocoaPods +import PromiseKit +#endif + +/** + To import the `MKDirections` category: + + use_frameworks! + pod "PromiseKit/MapKit" + + And then in your sources: + + import PromiseKit +*/ +extension MKDirections { +#if swift(>=4.2) + /// Begins calculating the requested route information asynchronously. + public func calculate() -> Promise { + return Promise(cancellableTask: MKDirectionsTask(self)) { calculate(completionHandler: $0.resolve) } + } + + /// Begins calculating the requested travel-time information asynchronously. + public func calculateETA() -> Promise { + return Promise(cancellableTask: MKDirectionsTask(self)) { calculateETA(completionHandler: $0.resolve) } + } +#else + /// Begins calculating the requested route information asynchronously. + public func calculate() -> Promise { + return Promise(cancellableTask: MKDirectionsTask(self)) { calculate(completionHandler: $0.resolve) } + } + + /// Begins calculating the requested travel-time information asynchronously. + public func calculateETA() -> Promise { + return Promise(cancellableTask: MKDirectionsTask(self)) { calculateETA(completionHandler: $0.resolve) } + } +#endif +} + +private class MKDirectionsTask: CancellableTask { + let directions: MKDirections + var cancelAttempted = false + + init(_ directions: MKDirections) { + self.directions = directions + } + + func cancel() { + directions.cancel() + cancelAttempted = true + } + + var isCancelled: Bool { + return cancelAttempted && !directions.isCalculating + } +} + +//////////////////////////////////////////////////////////// Cancellable wrappers + +extension MKDirections { +#if swift(>=4.2) + /// Begins calculating the requested route information asynchronously. + public func cancellableCalculate() -> CancellablePromise { + return cancellable(calculate()) + } + + /// Begins calculating the requested travel-time information asynchronously. + public func cancellableCalculateETA() -> CancellablePromise { + return cancellable(calculateETA()) + } +#else + /// Begins calculating the requested route information asynchronously. + public func cancellableCalculate() -> CancellablePromise { + return cancellable(calculate()) + } + + /// Begins calculating the requested travel-time information asynchronously. + public func cancellableCalculateETA() -> CancellablePromise { + return cancellable(calculateETA()) + } +#endif +} \ No newline at end of file diff --git a/Sources/MKMapSnapshotter+Promise.swift b/Sources/MKMapSnapshotter+Promise.swift index 45d590a..7095545 100644 --- a/Sources/MKMapSnapshotter+Promise.swift +++ b/Sources/MKMapSnapshotter+Promise.swift @@ -1,21 +1,62 @@ -import MapKit -#if !PMKCocoaPods -import PromiseKit -#endif - -/** - To import the `MKMapSnapshotter` category: - - use_frameworks! - pod "PromiseKit/MapKit" - - And then in your sources: - - import PromiseKit -*/ -extension MKMapSnapshotter { - /// Starts generating the snapshot using the options set in this object. - public func start() -> Promise { - return Promise { start(completionHandler: $0.resolve) } - } -} +import MapKit +#if !PMKCocoaPods +import PromiseKit +#endif + +/** + To import the `MKMapSnapshotter` category: + + use_frameworks! + pod "PromiseKit/MapKit" + + And then in your sources: + + import PromiseKit +*/ +extension MKMapSnapshotter { +#if swift(>=4.2) + /// Starts generating the snapshot using the options set in this object. + public func start() -> Promise { + return Promise(cancellableTask: MKMapSnapshotterTask(self)) { start(completionHandler: $0.resolve) } + } +#else + /// Starts generating the snapshot using the options set in this object. + public func start() -> Promise { + return Promise(cancellableTask: MKMapSnapshotterTask(self)) { start(completionHandler: $0.resolve) } + } +#endif +} + +private class MKMapSnapshotterTask: CancellableTask { + let snapshotter: MKMapSnapshotter + var cancelAttempted = false + + init(_ snapshotter: MKMapSnapshotter) { + self.snapshotter = snapshotter + } + + func cancel() { + snapshotter.cancel() + cancelAttempted = true + } + + var isCancelled: Bool { + return cancelAttempted && !snapshotter.isLoading + } +} + +//////////////////////////////////////////////////////////// Cancellable wrapper + +extension MKMapSnapshotter { +#if swift(>=4.2) + /// Starts generating the snapshot using the options set in this object. + public func cancellableStart() -> CancellablePromise { + return cancellable(start()) + } +#else + /// Starts generating the snapshot using the options set in this object. + public func cancellableStart() -> CancellablePromise { + return cancellable(start()) + } +#endif +} \ No newline at end of file diff --git a/Tests/TestMapKit.swift b/Tests/TestMapKit.swift index 41cbdd6..de25171 100644 --- a/Tests/TestMapKit.swift +++ b/Tests/TestMapKit.swift @@ -61,3 +61,70 @@ class Test_MKSnapshotter_Swift: XCTestCase { waitForExpectations(timeout: 1, handler: nil) } } + +//////////////////////////////////////////////////////////// Cancellation + +extension Test_MKDirections_Swift { + func test_cancel_directions_response() { + let ex = expectation(description: "") + + class MockDirections: MKDirections { + override func calculate(completionHandler: @escaping MKDirectionsHandler) { + completionHandler(MKDirectionsResponse(), nil) + } + } + + let rq = MKDirectionsRequest() + let directions = MockDirections(request: rq) + + directions.cancellableCalculate().done { _ in + XCTFail() + }.catch(policy: .allErrors) { + $0.isCancelled ? ex.fulfill() : XCTFail() + }.cancel() + + waitForExpectations(timeout: 1, handler: nil) + } + + + func test_cancel_ETA_response() { + let ex = expectation(description: "") + + class MockDirections: MKDirections { + override func calculateETA(completionHandler: @escaping MKETAHandler) { + completionHandler(MKETAResponse(), nil) + } + } + + let rq = MKDirectionsRequest() + MockDirections(request: rq).cancellableCalculateETA().done { _ in + XCTFail() + }.catch(policy: .allErrors) { + $0.isCancelled ? ex.fulfill() : XCTFail() + }.cancel() + + waitForExpectations(timeout: 1, handler: nil) + } + +} + +extension Test_MKSnapshotter_Swift { + func test_cancel() { + let ex = expectation(description: "") + + class MockSnapshotter: MKMapSnapshotter { + override func start(completionHandler: @escaping MKMapSnapshotCompletionHandler) { + completionHandler(MKMapSnapshot(), nil) + } + } + + let snapshotter = MockSnapshotter() + snapshotter.cancellableStart().done { _ in + XCTFail() + }.catch(policy: .allErrors) { + $0.isCancelled ? ex.fulfill() : XCTFail() + }.cancel() + + waitForExpectations(timeout: 1, handler: nil) + } +} From 747be0566537d536d49917bb507bbfdb9799f649 Mon Sep 17 00:00:00 2001 From: dougzilla32 Date: Tue, 16 Oct 2018 11:53:02 +0900 Subject: [PATCH 2/2] 'Cancel' for PromiseKit -- remove cancellable wrappers (they are unnecessary) --- Sources/MKDirections+Promise.swift | 34 ++++++-------------------- Sources/MKMapSnapshotter+Promise.swift | 20 +++------------ Tests/TestMapKit.swift | 6 ++--- 3 files changed, 15 insertions(+), 45 deletions(-) diff --git a/Sources/MKDirections+Promise.swift b/Sources/MKDirections+Promise.swift index 24b005a..fe18db3 100644 --- a/Sources/MKDirections+Promise.swift +++ b/Sources/MKDirections+Promise.swift @@ -16,21 +16,29 @@ import PromiseKit extension MKDirections { #if swift(>=4.2) /// Begins calculating the requested route information asynchronously. + /// - Note: cancelling this promise will cancel the underlying task + /// - SeeAlso: [Cancellation](http://promisekit.org/docs/) public func calculate() -> Promise { return Promise(cancellableTask: MKDirectionsTask(self)) { calculate(completionHandler: $0.resolve) } } /// Begins calculating the requested travel-time information asynchronously. + /// - Note: cancelling this promise will cancel the underlying task + /// - SeeAlso: [Cancellation](http://promisekit.org/docs/) public func calculateETA() -> Promise { return Promise(cancellableTask: MKDirectionsTask(self)) { calculateETA(completionHandler: $0.resolve) } } #else /// Begins calculating the requested route information asynchronously. + /// - Note: cancelling this promise will cancel the underlying task + /// - SeeAlso: [Cancellation](http://promisekit.org/docs/) public func calculate() -> Promise { return Promise(cancellableTask: MKDirectionsTask(self)) { calculate(completionHandler: $0.resolve) } } /// Begins calculating the requested travel-time information asynchronously. + /// - Note: cancelling this promise will cancel the underlying task + /// - SeeAlso: [Cancellation](http://promisekit.org/docs/) public func calculateETA() -> Promise { return Promise(cancellableTask: MKDirectionsTask(self)) { calculateETA(completionHandler: $0.resolve) } } @@ -54,29 +62,3 @@ private class MKDirectionsTask: CancellableTask { return cancelAttempted && !directions.isCalculating } } - -//////////////////////////////////////////////////////////// Cancellable wrappers - -extension MKDirections { -#if swift(>=4.2) - /// Begins calculating the requested route information asynchronously. - public func cancellableCalculate() -> CancellablePromise { - return cancellable(calculate()) - } - - /// Begins calculating the requested travel-time information asynchronously. - public func cancellableCalculateETA() -> CancellablePromise { - return cancellable(calculateETA()) - } -#else - /// Begins calculating the requested route information asynchronously. - public func cancellableCalculate() -> CancellablePromise { - return cancellable(calculate()) - } - - /// Begins calculating the requested travel-time information asynchronously. - public func cancellableCalculateETA() -> CancellablePromise { - return cancellable(calculateETA()) - } -#endif -} \ No newline at end of file diff --git a/Sources/MKMapSnapshotter+Promise.swift b/Sources/MKMapSnapshotter+Promise.swift index 7095545..4845ecc 100644 --- a/Sources/MKMapSnapshotter+Promise.swift +++ b/Sources/MKMapSnapshotter+Promise.swift @@ -16,11 +16,15 @@ import PromiseKit extension MKMapSnapshotter { #if swift(>=4.2) /// Starts generating the snapshot using the options set in this object. + /// - Note: cancelling this promise will cancel the underlying task + /// - SeeAlso: [Cancellation](http://promisekit.org/docs/) public func start() -> Promise { return Promise(cancellableTask: MKMapSnapshotterTask(self)) { start(completionHandler: $0.resolve) } } #else /// Starts generating the snapshot using the options set in this object. + /// - Note: cancelling this promise will cancel the underlying task + /// - SeeAlso: [Cancellation](http://promisekit.org/docs/) public func start() -> Promise { return Promise(cancellableTask: MKMapSnapshotterTask(self)) { start(completionHandler: $0.resolve) } } @@ -44,19 +48,3 @@ private class MKMapSnapshotterTask: CancellableTask { return cancelAttempted && !snapshotter.isLoading } } - -//////////////////////////////////////////////////////////// Cancellable wrapper - -extension MKMapSnapshotter { -#if swift(>=4.2) - /// Starts generating the snapshot using the options set in this object. - public func cancellableStart() -> CancellablePromise { - return cancellable(start()) - } -#else - /// Starts generating the snapshot using the options set in this object. - public func cancellableStart() -> CancellablePromise { - return cancellable(start()) - } -#endif -} \ No newline at end of file diff --git a/Tests/TestMapKit.swift b/Tests/TestMapKit.swift index de25171..fb3197d 100644 --- a/Tests/TestMapKit.swift +++ b/Tests/TestMapKit.swift @@ -77,7 +77,7 @@ extension Test_MKDirections_Swift { let rq = MKDirectionsRequest() let directions = MockDirections(request: rq) - directions.cancellableCalculate().done { _ in + cancellable(directions.calculate()).done { _ in XCTFail() }.catch(policy: .allErrors) { $0.isCancelled ? ex.fulfill() : XCTFail() @@ -97,7 +97,7 @@ extension Test_MKDirections_Swift { } let rq = MKDirectionsRequest() - MockDirections(request: rq).cancellableCalculateETA().done { _ in + cancellable(MockDirections(request: rq).calculateETA()).done { _ in XCTFail() }.catch(policy: .allErrors) { $0.isCancelled ? ex.fulfill() : XCTFail() @@ -119,7 +119,7 @@ extension Test_MKSnapshotter_Swift { } let snapshotter = MockSnapshotter() - snapshotter.cancellableStart().done { _ in + cancellable(snapshotter.start()).done { _ in XCTFail() }.catch(policy: .allErrors) { $0.isCancelled ? ex.fulfill() : XCTFail()