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 8d4fefc..80a4000 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1 +1 @@ -github "mxcl/PromiseKit" "6.3.4" +github "dougzilla32/PromiseKit" "087b3cf470890ff9ea841212e2f3e285fecf3988" diff --git a/Sources/HMAcessoryBrowser+Promise.swift b/Sources/HMAcessoryBrowser+Promise.swift index 06dec36..816c1dd 100644 --- a/Sources/HMAcessoryBrowser+Promise.swift +++ b/Sources/HMAcessoryBrowser+Promise.swift @@ -24,6 +24,7 @@ public class HMPromiseAccessoryBrowser { private class BrowserProxy: PromiseProxy<[HMAccessory]>, HMAccessoryBrowserDelegate { let browser = HMAccessoryBrowser() let scanInterval: ScanInterval + var timer: CancellablePromise? init(scanInterval: ScanInterval) { self.scanInterval = scanInterval @@ -40,7 +41,7 @@ private class BrowserProxy: PromiseProxy<[HMAccessory]>, HMAccessoryBrowserDeleg } if let timeout = timeout { - after(seconds: timeout) + self.timer = cancellable(after(seconds: timeout)) .done { [weak self] () -> Void in guard let _self = self else { return } _self.reject(HMPromiseAccessoryBrowserError.noAccessoryFound) @@ -60,6 +61,7 @@ private class BrowserProxy: PromiseProxy<[HMAccessory]>, HMAccessoryBrowserDeleg override func cancel() { browser.stopSearchingForNewAccessories() + timer?.cancel() super.cancel() } @@ -74,3 +76,11 @@ private class BrowserProxy: PromiseProxy<[HMAccessory]>, HMAccessoryBrowserDeleg } #endif + +//////////////////////////////////////////////////////////// Cancellable wrapper + +extension HMPromiseAccessoryBrowser { + public func cancellableStart(scanInterval: ScanInterval) -> CancellablePromise<[HMAccessory]> { + return cancellable(start(scanInterval: scanInterval)) + } +} diff --git a/Sources/HMHomeManager+Promise.swift b/Sources/HMHomeManager+Promise.swift index a1d67fa..0318832 100644 --- a/Sources/HMHomeManager+Promise.swift +++ b/Sources/HMHomeManager+Promise.swift @@ -43,18 +43,34 @@ extension HMHomeManager { internal class HMHomeManagerProxy: PromiseProxy<[HMHome]>, HMHomeManagerDelegate { fileprivate let manager: HMHomeManager + private var task: DispatchWorkItem! override init() { self.manager = HMHomeManager() super.init() self.manager.delegate = self - DispatchQueue.main.asyncAfter(deadline: .now() + 20.0) { [weak self] in + self.task = DispatchWorkItem { [weak self] in self?.reject(HomeKitError.permissionDeined) } + DispatchQueue.main.asyncAfter(deadline: .now() + 20.0, execute: self.task) } func homeManagerDidUpdateHomes(_ manager: HMHomeManager) { fulfill(manager.homes) } + + override func cancel() { + self.task.cancel() + super.cancel() + } +} + +//////////////////////////////////////////////////////////// Cancellable wrapper + +@available(iOS 8.0, tvOS 10.0, *) +extension HMHomeManager { + public func cancellableHomes() -> CancellablePromise<[HMHome]> { + return cancellable(homes()) + } } diff --git a/Sources/Utils.swift b/Sources/Utils.swift index f114789..cffba90 100644 --- a/Sources/Utils.swift +++ b/Sources/Utils.swift @@ -3,7 +3,9 @@ import PromiseKit /** Commonly used functionality when promisifying a delegate pattern */ -internal class PromiseProxy: NSObject { +internal class PromiseProxy: NSObject, CancellableTask { + var isCancelled = false + internal let (promise, seal) = Promise.pending(); private var retainCycle: PromiseProxy? @@ -13,7 +15,9 @@ internal class PromiseProxy: NSObject { // Create a retain cycle self.retainCycle = self // And ensure we break it when the promise is resolved - _ = promise.ensure { self.retainCycle = nil } + _ = promise.ensure { self.retainCycle = nil ; self.promise.setCancellableTask(nil) } + + promise.setCancellableTask(self) } /// These functions ensure we only resolve the promise once @@ -28,6 +32,7 @@ internal class PromiseProxy: NSObject { /// Cancel helper internal func cancel() { + isCancelled = true self.reject(PMKError.cancelled) } } diff --git a/Tests/HMAccessoryBrowserTests.swift b/Tests/HMAccessoryBrowserTests.swift index b6f555f..5318fb2 100644 --- a/Tests/HMAccessoryBrowserTests.swift +++ b/Tests/HMAccessoryBrowserTests.swift @@ -46,7 +46,7 @@ extension HMAccessoryBrowser { @objc func pmk_startSearchingForNewAccessories() { after(.milliseconds(100)) .done { swag in - self.delegate!.accessoryBrowser?(self, didFindNewAccessory: MockAccessory()) + self.delegate?.accessoryBrowser?(self, didFindNewAccessory: MockAccessory()) } } } @@ -81,4 +81,36 @@ func swizzle(_ foo: AnyClass, _ from: Selector, isClassMethod: Bool = false, bod method_exchangeImplementations(swizzledMethod, originalMethod) } +//////////////////////////////////////////////////////////// Cancellation + +extension HMAccessoryBrowserTests { + + func testCancelBrowserScanReturningFirst() { + swizzle(HMAccessoryBrowser.self, #selector(HMAccessoryBrowser.startSearchingForNewAccessories)) { + let ex = expectation(description: "") + + cancellable(HMPromiseAccessoryBrowser().start(scanInterval: .returnFirst(timeout: 0.5))) + .done { accessories in + XCTAssertEqual(accessories.count, 1) + XCTFail() + }.catch(policy: .allErrors) { + $0.isCancelled ? ex.fulfill() : XCTFail() + }.cancel() + + waitForExpectations(timeout: 1, handler: nil) + } + } + + func testCancelBrowserScanReturningTimeout() { + let ex = expectation(description: "") + + cancellable(HMPromiseAccessoryBrowser().start(scanInterval: .returnFirst(timeout: 0.5))) + .catch(policy: .allErrors) { + $0.isCancelled ? ex.fulfill() : XCTFail() + }.cancel() + + waitForExpectations(timeout: 1, handler: nil) + } +} + #endif