Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

'Cancel' for PromiseKit #3

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.4"
github "dougzilla32/PromiseKit" "087b3cf470890ff9ea841212e2f3e285fecf3988"
6 changes: 5 additions & 1 deletion Sources/HMAcessoryBrowser+Promise.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public enum HMPromiseAccessoryBrowserError: Error {
public class HMPromiseAccessoryBrowser {
private var proxy: BrowserProxy?

/// - Note: cancelling this promise will cancel the underlying task
/// - SeeAlso: [Cancellation](http://promisekit.org/docs/)
public func start(scanInterval: ScanInterval) -> Promise<[HMAccessory]> {
proxy = BrowserProxy(scanInterval: scanInterval)
return proxy!.promise
Expand All @@ -24,6 +26,7 @@ public class HMPromiseAccessoryBrowser {
private class BrowserProxy: PromiseProxy<[HMAccessory]>, HMAccessoryBrowserDelegate {
let browser = HMAccessoryBrowser()
let scanInterval: ScanInterval
var timer: CancellablePromise<Void>?

init(scanInterval: ScanInterval) {
self.scanInterval = scanInterval
Expand All @@ -40,7 +43,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)
Expand All @@ -60,6 +63,7 @@ private class BrowserProxy: PromiseProxy<[HMAccessory]>, HMAccessoryBrowserDeleg

override func cancel() {
browser.stopSearchingForNewAccessories()
timer?.cancel()
super.cancel()
}

Expand Down
11 changes: 10 additions & 1 deletion Sources/HMHomeManager+Promise.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ public enum HomeKitError: Error {

@available(iOS 8.0, tvOS 10.0, *)
extension HMHomeManager {
/// - Note: cancelling this promise will cancel the underlying task
/// - SeeAlso: [Cancellation](http://promisekit.org/docs/)
public func homes() -> Promise<[HMHome]> {
return HMHomeManagerProxy().promise
}
Expand Down Expand Up @@ -43,18 +45,25 @@ 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()
}
}
9 changes: 7 additions & 2 deletions Sources/Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import PromiseKit
/**
Commonly used functionality when promisifying a delegate pattern
*/
internal class PromiseProxy<T>: NSObject {
internal class PromiseProxy<T>: NSObject, CancellableTask {
var isCancelled = false

internal let (promise, seal) = Promise<T>.pending();

private var retainCycle: PromiseProxy?
Expand All @@ -13,7 +15,9 @@ internal class PromiseProxy<T>: 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
Expand All @@ -28,6 +32,7 @@ internal class PromiseProxy<T>: NSObject {

/// Cancel helper
internal func cancel() {
isCancelled = true
self.reject(PMKError.cancelled)
}
}
Expand Down
34 changes: 33 additions & 1 deletion Tests/HMAccessoryBrowserTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
}
}
Expand Down Expand Up @@ -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