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 8d4fefc..80a4000 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1 +1 @@ -github "mxcl/PromiseKit" "6.3.4" +github "dougzilla32/PromiseKit" "087b3cf470890ff9ea841212e2f3e285fecf3988" diff --git a/Sources/MKDirections+Promise.swift b/Sources/MKDirections+Promise.swift index 2230d46..fe18db3 100644 --- a/Sources/MKDirections+Promise.swift +++ b/Sources/MKDirections+Promise.swift @@ -1,38 +1,64 @@ -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 { calculate(completionHandler: $0.resolve) } - } - - /// Begins calculating the requested travel-time information asynchronously. - public func calculateETA() -> Promise { - return Promise { calculateETA(completionHandler: $0.resolve) } - } -#else - /// 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) } - } -#endif -} +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. + /// - 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) } + } +#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 + } +} diff --git a/Sources/MKMapSnapshotter+Promise.swift b/Sources/MKMapSnapshotter+Promise.swift index 768cfb7..4845ecc 100644 --- a/Sources/MKMapSnapshotter+Promise.swift +++ b/Sources/MKMapSnapshotter+Promise.swift @@ -1,28 +1,50 @@ -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 { start(completionHandler: $0.resolve) } - } -#else - /// Starts generating the snapshot using the options set in this object. - public func start() -> Promise { - return Promise { start(completionHandler: $0.resolve) } - } -#endif -} +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. + /// - 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) } + } +#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 + } +} diff --git a/Tests/TestMapKit.swift b/Tests/TestMapKit.swift index 41cbdd6..fb3197d 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) + + cancellable(directions.calculate()).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() + cancellable(MockDirections(request: rq).calculateETA()).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() + cancellable(snapshotter.start()).done { _ in + XCTFail() + }.catch(policy: .allErrors) { + $0.isCancelled ? ex.fulfill() : XCTFail() + }.cancel() + + waitForExpectations(timeout: 1, handler: nil) + } +}