Skip to content

Commit

Permalink
'Cancel' for PromiseKit -- provides the ability to cancel promises an…
Browse files Browse the repository at this point in the history
…d promise chains
  • Loading branch information
dougzilla32 committed Oct 7, 2018
1 parent de16bbe commit 9f96ec4
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 5 deletions.
3 changes: 2 additions & 1 deletion Cartfile
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
github "mxcl/PromiseKit" ~> 6.0
#github "mxcl/PromiseKit" ~> 6.0
github "dougzilla32/PromiseKit" "CoreCancel"
2 changes: 1 addition & 1 deletion Cartfile.resolved
Original file line number Diff line number Diff line change
@@ -1 +1 @@
github "mxcl/PromiseKit" "6.3.3"
github "dougzilla32/PromiseKit" "087b3cf470890ff9ea841212e2f3e285fecf3988"
32 changes: 30 additions & 2 deletions Sources/SKProductsRequest+Promise.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ extension SKProductsRequest {
- Returns: A promise that fulfills if the request succeeds.
*/
public func start(_: PMKNamespacer) -> Promise<SKProductsResponse> {
let proxy = SKDelegate()
let proxy = SKDelegate(request: self)
delegate = proxy
proxy.retainCycle = proxy
start()
Expand All @@ -29,10 +29,17 @@ extension SKProductsRequest {
}


fileprivate class SKDelegate: NSObject, SKProductsRequestDelegate {
fileprivate class SKDelegate: NSObject, SKProductsRequestDelegate, CancellableTask {
let (promise, seal) = Promise<SKProductsResponse>.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
Expand All @@ -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…
Expand All @@ -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<SKProductsResponse> {
return cancellable(start(.promise))
}
}
19 changes: 18 additions & 1 deletion Sources/SKReceiptRefreshRequest+Promise.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ extension SKReceiptRefreshRequest {
}
}

private class ReceiptRefreshObserver: NSObject, SKRequestDelegate {
private class ReceiptRefreshObserver: NSObject, SKRequestDelegate, CancellableTask {
let (promise, seal) = Promise<SKReceiptRefreshRequest>.pending()
let request: SKReceiptRefreshRequest
var retainCycle: ReceiptRefreshObserver?
Expand All @@ -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<SKReceiptRefreshRequest> {
let rro = ReceiptRefreshObserver(request: self)
return CancellablePromise(task: rro, promise: rro.promise, resolver: rro.seal)
}
}
30 changes: 30 additions & 0 deletions Tests/TestStoreKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

0 comments on commit 9f96ec4

Please sign in to comment.