fix: observer leak would throw and crash the app sometimes
This commit is contained in:
1 parent
9a8c83e39d
commit
9ca28eb2d8
3 files changed
+21
-7
No files matched your search
@@ -15,11 +15,11 @@ extension Optional {
|
|||||||
// add throw-on-nil method on Optional
|
// add throw-on-nil method on Optional
|
||||||
func orThrow() throws -> Wrapped {
|
func orThrow() throws -> Wrapped {
|
||||||
switch self {
|
switch self {
|
||||||
case .some(let value):
|
case .some(let value):
|
||||||
return value
|
return value
|
||||||
case .none:
|
case .none:
|
||||||
Thread.callStackSymbols.forEach { print($0) }
|
Thread.callStackSymbols.forEach { print($0) }
|
||||||
throw NSError.make(domain: "Optional", message: "Optional contained nil")
|
throw NSError.make(domain: "Optional", message: "Optional contained nil")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,3 +64,11 @@ extension Collection {
|
|||||||
return flatMap { ($0 as? [Any])?.joined() ?? [$0] }
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,10 @@ class Application: NSObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func removeObserver() {
|
||||||
|
runningApplication.safeRemoveObserver(self, "isFinishedLaunching")
|
||||||
|
}
|
||||||
|
|
||||||
private func addAndObserveWindows() {
|
private func addAndObserveWindows() {
|
||||||
axUiElement = AXUIElementCreateApplication(runningApplication.processIdentifier)
|
axUiElement = AXUIElementCreateApplication(runningApplication.processIdentifier)
|
||||||
AXObserverCreate(runningApplication.processIdentifier, axObserverApplicationCallback, &axObserver)
|
AXObserverCreate(runningApplication.processIdentifier, axObserverApplicationCallback, &axObserver)
|
||||||
@@ -44,7 +48,7 @@ class Application: NSObject {
|
|||||||
|
|
||||||
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
|
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
|
||||||
guard let isFinishedLaunching = change![.newKey], isFinishedLaunching as! Bool else { return }
|
guard let isFinishedLaunching = change![.newKey], isFinishedLaunching as! Bool else { return }
|
||||||
runningApplication.removeObserver(self, forKeyPath: "isFinishedLaunching")
|
removeObserver()
|
||||||
addAndObserveWindows()
|
addAndObserveWindows()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,13 +30,15 @@ class Applications {
|
|||||||
var someAppsAreAlreadyTerminated = false
|
var someAppsAreAlreadyTerminated = false
|
||||||
for runningApp in runningApps {
|
for runningApp in runningApps {
|
||||||
guard runningApp.bundleIdentifier != nil else { someAppsAreAlreadyTerminated = true; continue }
|
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]()
|
var windowsToKeep = [Window]()
|
||||||
for window in Windows.listRecentlyUsedFirst {
|
for window in Windows.listRecentlyUsedFirst {
|
||||||
guard window.application.runningApplication.processIdentifier != runningApp.processIdentifier else { continue }
|
guard window.application.runningApplication.processIdentifier != runningApp.processIdentifier else { continue }
|
||||||
windowsToKeep.append(window)
|
windowsToKeep.append(window)
|
||||||
}
|
}
|
||||||
Windows.listRecentlyUsedFirst = windowsToKeep
|
Windows.listRecentlyUsedFirst = windowsToKeep
|
||||||
|
// some apps never finish launching; the observer leaks for them without this
|
||||||
|
app.removeObserver()
|
||||||
Applications.map.removeValue(forKey: runningApp.processIdentifier)
|
Applications.map.removeValue(forKey: runningApp.processIdentifier)
|
||||||
guard Windows.listRecentlyUsedFirst.count > 0 else { (App.shared as! App).hideUi(); return }
|
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
|
// TODO: implement of more sophisticated way to decide which thumbnail gets focused on app quit
|
||||||
|
|||||||
Reference in new issue
Block a user