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/SKProductsRequest+Promise.swift b/Sources/SKProductsRequest+Promise.swift index 5497fcd..1910d71 100644 --- a/Sources/SKProductsRequest+Promise.swift +++ b/Sources/SKProductsRequest+Promise.swift @@ -18,9 +18,11 @@ extension SKProductsRequest { Sends the request to the Apple App Store. - Returns: A promise that fulfills if the request succeeds. + - Note: cancelling this promise will cancel the underlying task + - SeeAlso: [Cancellation](http://promisekit.org/docs/) */ public func start(_: PMKNamespacer) -> Promise { - let proxy = SKDelegate() + let proxy = SKDelegate(request: self) delegate = proxy proxy.retainCycle = proxy start() @@ -29,10 +31,17 @@ extension SKProductsRequest { } -fileprivate class SKDelegate: NSObject, SKProductsRequestDelegate { +fileprivate class SKDelegate: NSObject, SKProductsRequestDelegate, CancellableTask { let (promise, seal) = Promise.pending() + let request: SKRequest var retainCycle: SKDelegate? + init(request: SKRequest) { + self.request = request + super.init() + promise.setCancellableTask(self, reject: seal.reject) + } + @objc fileprivate func request(_ request: SKRequest, didFailWithError error: Error) { seal.reject(error) retainCycle = nil @@ -42,6 +51,14 @@ fileprivate class SKDelegate: NSObject, SKProductsRequestDelegate { seal.fulfill(response) retainCycle = nil } + + var isCancelled = false + + func cancel() { + request.cancel() + retainCycle = nil + isCancelled = true + } } // perhaps one day Apple will actually make their errors into Errors… diff --git a/Sources/SKReceiptRefreshRequest+Promise.swift b/Sources/SKReceiptRefreshRequest+Promise.swift index 3bbc784..8300a56 100644 --- a/Sources/SKReceiptRefreshRequest+Promise.swift +++ b/Sources/SKReceiptRefreshRequest+Promise.swift @@ -9,7 +9,7 @@ extension SKReceiptRefreshRequest { } } -private class ReceiptRefreshObserver: NSObject, SKRequestDelegate { +private class ReceiptRefreshObserver: NSObject, SKRequestDelegate, CancellableTask { let (promise, seal) = Promise.pending() let request: SKReceiptRefreshRequest var retainCycle: ReceiptRefreshObserver? @@ -32,4 +32,21 @@ private class ReceiptRefreshObserver: NSObject, SKRequestDelegate { seal.reject(error) retainCycle = nil } + + var isCancelled = false + + func cancel() { + request.cancel() + retainCycle = nil + isCancelled = true + } +} + +//////////////////////////////////////////////////////////// Cancellation + +extension SKReceiptRefreshRequest { + public func cancellablePromise() -> CancellablePromise { + let rro = ReceiptRefreshObserver(request: self) + return CancellablePromise(task: rro, promise: rro.promise, resolver: rro.seal) + } } diff --git a/Tests/TestStoreKit.swift b/Tests/TestStoreKit.swift index e40ae84..7c76f00 100644 --- a/Tests/TestStoreKit.swift +++ b/Tests/TestStoreKit.swift @@ -20,3 +20,33 @@ class SKProductsRequestTests: XCTestCase { waitForExpectations(timeout: 1, handler: nil) } } + +//////////////////////////////////////////////////////////// Cancellation + +extension SKProductsRequestTests { + func testCancel() { + class MockProductsRequest: SKProductsRequest { + var isCancelled = false + + override func start() { + after(seconds: 0.1).done { + if !self.isCancelled { + self.delegate?.productsRequest(self, didReceive: SKProductsResponse()) + } + } + } + } + + let ex = expectation(description: "") + + let request = MockProductsRequest() + cancellable(request.start(.promise)).done { _ in + XCTFail() + }.catch(policy: .allErrors) { + $0.isCancelled ? ex.fulfill() : XCTFail() + }.cancel() + request.isCancelled = true + + waitForExpectations(timeout: 1, handler: nil) + } +}