Skip to content

Commit

Permalink
fix: observer leak would throw and crash the app sometimes
Browse files Browse the repository at this point in the history
  • Loading branch information
louis.pontoise authored and lwouis committed Mar 10, 2020
1 parent 9a8c83e commit 9ca28eb
Show file tree
Hide file tree
Showing 3 changed files with 21 additions and 7 deletions.
18 changes: 13 additions & 5 deletions alt-tab-macos/api-wrappers/HelperExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ extension Optional {
// add throw-on-nil method on Optional
func orThrow() throws -> Wrapped {
switch self {
case .some(let value):
return value
case .none:
Thread.callStackSymbols.forEach { print($0) }
throw NSError.make(domain: "Optional", message: "Optional contained nil")
case .some(let value):
return value
case .none:
Thread.callStackSymbols.forEach { print($0) }
throw NSError.make(domain: "Optional", message: "Optional contained nil")
}
}
}
Expand Down Expand Up @@ -64,3 +64,11 @@ extension Collection {
return flatMap { ($0 as? [Any])?.joined() ?? [$0] }
}
}

// removing an objc KVO observer if there is none throws an exception
extension NSObject {
func safeRemoveObserver(_ observer: NSObject, _ key: String) {
guard observationInfo != nil else { return }
removeObserver(observer, forKeyPath: key)
}
}
6 changes: 5 additions & 1 deletion alt-tab-macos/logic/Application.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ class Application: NSObject {
}
}

func removeObserver() {
runningApplication.safeRemoveObserver(self, "isFinishedLaunching")
}

private func addAndObserveWindows() {
axUiElement = AXUIElementCreateApplication(runningApplication.processIdentifier)
AXObserverCreate(runningApplication.processIdentifier, axObserverApplicationCallback, &axObserver)
Expand Down Expand Up @@ -44,7 +48,7 @@ class Application: NSObject {

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
guard let isFinishedLaunching = change![.newKey], isFinishedLaunching as! Bool else { return }
runningApplication.removeObserver(self, forKeyPath: "isFinishedLaunching")
removeObserver()
addAndObserveWindows()
}

Expand Down
4 changes: 3 additions & 1 deletion alt-tab-macos/logic/Applications.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@ class Applications {
var someAppsAreAlreadyTerminated = false
for runningApp in runningApps {
guard runningApp.bundleIdentifier != nil else { someAppsAreAlreadyTerminated = true; continue }
guard Applications.map[runningApp.processIdentifier] != nil else { continue }
guard let app = Applications.map[runningApp.processIdentifier] else { continue }
var windowsToKeep = [Window]()
for window in Windows.listRecentlyUsedFirst {
guard window.application.runningApplication.processIdentifier != runningApp.processIdentifier else { continue }
windowsToKeep.append(window)
}
Windows.listRecentlyUsedFirst = windowsToKeep
// some apps never finish launching; the observer leaks for them without this
app.removeObserver()
Applications.map.removeValue(forKey: runningApp.processIdentifier)
guard Windows.listRecentlyUsedFirst.count > 0 else { (App.shared as! App).hideUi(); return }
// TODO: implement of more sophisticated way to decide which thumbnail gets focused on app quit
Expand Down

0 comments on commit 9ca28eb

Please sign in to comment.