From 6261528d4a49fb36aad1d34ff760859ebf534d5a Mon Sep 17 00:00:00 2001 From: dougzilla32 Date: Tue, 25 Sep 2018 15:01:52 +0900 Subject: [PATCH] 'Cancel' for PromiseKit -- provides the ability to cancel promises and promise chains --- Cartfile | 3 +- Cartfile.resolved | 2 +- Sources/SKProductsRequest+Promise.swift | 32 +++++++++++++++++-- Sources/SKReceiptRefreshRequest+Promise.swift | 19 ++++++++++- Tests/TestStoreKit.swift | 30 +++++++++++++++++ 5 files changed, 81 insertions(+), 5 deletions(-) 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..9957912 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1 +1 @@ -github "mxcl/PromiseKit" "6.3.3" +github "dougzilla32/PromiseKit" "288f7fbabc0b33c558bf908a3a0770693223d4e0" diff --git a/Sources/SKProductsRequest+Promise.swift b/Sources/SKProductsRequest+Promise.swift index 5497fcd..58d932e 100644 --- a/Sources/SKProductsRequest+Promise.swift +++ b/Sources/SKProductsRequest+Promise.swift @@ -20,7 +20,7 @@ extension SKProductsRequest { - Returns: A promise that fulfills if the request succeeds. */ public func start(_: PMKNamespacer) -> Promise { - let proxy = SKDelegate() + let proxy = SKDelegate(request: self) delegate = proxy proxy.retainCycle = proxy start() @@ -29,10 +29,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 +49,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… @@ -50,3 +65,16 @@ fileprivate class SKDelegate: NSObject, SKProductsRequestDelegate { // return true // } //} + +//////////////////////////////////////////////////////////// Cancellable wrapper + +extension SKProductsRequest { + /** + Sends the request to the Apple App Store. + + - Returns: A cancellable promise that fulfills if the request succeeds. + */ + public func cancellableStart(_: PMKNamespacer) -> CancellablePromise { + return cancellable(start(.promise)) + } +} 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..fd1dc83 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() + request.cancellableStart(.promise).done { _ in + XCTFail() + }.catch(policy: .allErrors) { + $0.isCancelled ? ex.fulfill() : XCTFail() + }.cancel() + request.isCancelled = true + + waitForExpectations(timeout: 1, handler: nil) + } +}