Skip to content

Commit bcd797f

Browse files
committed
'Cancel' for PromiseKit -- provides the ability to cancel promises and promise chains
1 parent e56adc9 commit bcd797f

File tree

6 files changed

+71
-7
lines changed

6 files changed

+71
-7
lines changed

Cartfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
github "mxcl/PromiseKit" ~> 6.0
1+
#github "mxcl/PromiseKit" ~> 6.0
2+
github "dougzilla32/PromiseKit" "CoreCancel"

Cartfile.resolved

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
github "mxcl/PromiseKit" "6.3.4"
1+
github "dougzilla32/PromiseKit" "087b3cf470890ff9ea841212e2f3e285fecf3988"

Sources/HMAcessoryBrowser+Promise.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public class HMPromiseAccessoryBrowser {
2424
private class BrowserProxy: PromiseProxy<[HMAccessory]>, HMAccessoryBrowserDelegate {
2525
let browser = HMAccessoryBrowser()
2626
let scanInterval: ScanInterval
27+
var timer: CancellablePromise<Void>?
2728

2829
init(scanInterval: ScanInterval) {
2930
self.scanInterval = scanInterval
@@ -40,7 +41,7 @@ private class BrowserProxy: PromiseProxy<[HMAccessory]>, HMAccessoryBrowserDeleg
4041
}
4142

4243
if let timeout = timeout {
43-
after(seconds: timeout)
44+
self.timer = cancellable(after(seconds: timeout))
4445
.done { [weak self] () -> Void in
4546
guard let _self = self else { return }
4647
_self.reject(HMPromiseAccessoryBrowserError.noAccessoryFound)
@@ -60,6 +61,7 @@ private class BrowserProxy: PromiseProxy<[HMAccessory]>, HMAccessoryBrowserDeleg
6061

6162
override func cancel() {
6263
browser.stopSearchingForNewAccessories()
64+
timer?.cancel()
6365
super.cancel()
6466
}
6567

@@ -74,3 +76,11 @@ private class BrowserProxy: PromiseProxy<[HMAccessory]>, HMAccessoryBrowserDeleg
7476
}
7577

7678
#endif
79+
80+
//////////////////////////////////////////////////////////// Cancellable wrapper
81+
82+
extension HMPromiseAccessoryBrowser {
83+
public func cancellableStart(scanInterval: ScanInterval) -> CancellablePromise<[HMAccessory]> {
84+
return cancellable(start(scanInterval: scanInterval))
85+
}
86+
}

Sources/HMHomeManager+Promise.swift

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,34 @@ extension HMHomeManager {
4343
internal class HMHomeManagerProxy: PromiseProxy<[HMHome]>, HMHomeManagerDelegate {
4444

4545
fileprivate let manager: HMHomeManager
46+
private var task: DispatchWorkItem!
4647

4748
override init() {
4849
self.manager = HMHomeManager()
4950
super.init()
5051
self.manager.delegate = self
5152

52-
DispatchQueue.main.asyncAfter(deadline: .now() + 20.0) { [weak self] in
53+
self.task = DispatchWorkItem { [weak self] in
5354
self?.reject(HomeKitError.permissionDeined)
5455
}
56+
DispatchQueue.main.asyncAfter(deadline: .now() + 20.0, execute: self.task)
5557
}
5658

5759
func homeManagerDidUpdateHomes(_ manager: HMHomeManager) {
5860
fulfill(manager.homes)
5961
}
62+
63+
override func cancel() {
64+
self.task.cancel()
65+
super.cancel()
66+
}
67+
}
68+
69+
//////////////////////////////////////////////////////////// Cancellable wrapper
70+
71+
@available(iOS 8.0, tvOS 10.0, *)
72+
extension HMHomeManager {
73+
public func cancellableHomes() -> CancellablePromise<[HMHome]> {
74+
return cancellable(homes())
75+
}
6076
}

Sources/Utils.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import PromiseKit
33
/**
44
Commonly used functionality when promisifying a delegate pattern
55
*/
6-
internal class PromiseProxy<T>: NSObject {
6+
internal class PromiseProxy<T>: NSObject, CancellableTask {
7+
var isCancelled = false
8+
79
internal let (promise, seal) = Promise<T>.pending();
810

911
private var retainCycle: PromiseProxy?
@@ -13,7 +15,9 @@ internal class PromiseProxy<T>: NSObject {
1315
// Create a retain cycle
1416
self.retainCycle = self
1517
// And ensure we break it when the promise is resolved
16-
_ = promise.ensure { self.retainCycle = nil }
18+
_ = promise.ensure { self.retainCycle = nil ; self.promise.setCancellableTask(nil) }
19+
20+
promise.setCancellableTask(self)
1721
}
1822

1923
/// These functions ensure we only resolve the promise once
@@ -28,6 +32,7 @@ internal class PromiseProxy<T>: NSObject {
2832

2933
/// Cancel helper
3034
internal func cancel() {
35+
isCancelled = true
3136
self.reject(PMKError.cancelled)
3237
}
3338
}

Tests/HMAccessoryBrowserTests.swift

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ extension HMAccessoryBrowser {
4646
@objc func pmk_startSearchingForNewAccessories() {
4747
after(.milliseconds(100))
4848
.done { swag in
49-
self.delegate!.accessoryBrowser?(self, didFindNewAccessory: MockAccessory())
49+
self.delegate?.accessoryBrowser?(self, didFindNewAccessory: MockAccessory())
5050
}
5151
}
5252
}
@@ -81,4 +81,36 @@ func swizzle(_ foo: AnyClass, _ from: Selector, isClassMethod: Bool = false, bod
8181
method_exchangeImplementations(swizzledMethod, originalMethod)
8282
}
8383

84+
//////////////////////////////////////////////////////////// Cancellation
85+
86+
extension HMAccessoryBrowserTests {
87+
88+
func testCancelBrowserScanReturningFirst() {
89+
swizzle(HMAccessoryBrowser.self, #selector(HMAccessoryBrowser.startSearchingForNewAccessories)) {
90+
let ex = expectation(description: "")
91+
92+
cancellable(HMPromiseAccessoryBrowser().start(scanInterval: .returnFirst(timeout: 0.5)))
93+
.done { accessories in
94+
XCTAssertEqual(accessories.count, 1)
95+
XCTFail()
96+
}.catch(policy: .allErrors) {
97+
$0.isCancelled ? ex.fulfill() : XCTFail()
98+
}.cancel()
99+
100+
waitForExpectations(timeout: 1, handler: nil)
101+
}
102+
}
103+
104+
func testCancelBrowserScanReturningTimeout() {
105+
let ex = expectation(description: "")
106+
107+
cancellable(HMPromiseAccessoryBrowser().start(scanInterval: .returnFirst(timeout: 0.5)))
108+
.catch(policy: .allErrors) {
109+
$0.isCancelled ? ex.fulfill() : XCTFail()
110+
}.cancel()
111+
112+
waitForExpectations(timeout: 1, handler: nil)
113+
}
114+
}
115+
84116
#endif

0 commit comments

Comments
 (0)