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) + } +}