macos: avoid notification publisher retain cycle (#13094)

Turns out combine's `publisher(for:,object:)` retains the object! We
verified this with a test script shown below. Fix this with a manual
filter. Found by @mustafa0x.

```
import Combine
import Foundation

final class Token {
    deinit { print("Token deinitialized") }
}

weak var weakToken: Token?
var publisher: NotificationCenter.Publisher?

// Create scope that will free token.
do {
    let token = Token()
    weakToken = token
    publisher = NotificationCenter.default.publisher(
        for: Notification.Name("TestNotification"),
        object: token
    )
}

print("Retained:", weakToken != nil)
publisher = nil
print("Released:", weakToken == nil)
```
This commit is contained in:
Mitchell Hashimoto
2026-06-25 11:26:25 -07:00
committed by GitHub

View File

@@ -292,7 +292,13 @@ extension Ghostty {
// A drag can emit multiple selection changes. Debounce so screen
// readers hear one announcement once the selection settles.
accessibilitySelectionCancellable = NotificationCenter.default
.publisher(for: .ghosttySelectionDidChange, object: self)
// The publisher retains its object, so filtering with a weak capture
// avoids a cycle between self and the stored cancellable.
.publisher(for: .ghosttySelectionDidChange)
.filter { [weak self] notification in
guard let self else { return false }
return notification.object as AnyObject? === self
}
.debounce(for: .milliseconds(100), scheduler: DispatchQueue.main)
.sink { [weak self] _ in
guard let self else { return }