Skip to content

Keep the configure sheet alive between reads - #12

Merged
winebarrel merged 1 commit into
mainfrom
fix-configure-sheet-lifetime
Aug 10, 2026
Merged

winebarrel merged 1 commit into
mainfrom
fix-configure-sheet-lifetime

Conversation

@winebarrel

Copy link
Copy Markdown
Owner

Pressing Options can do nothing at all. Found while chasing the symptom in
NecoSaver, which grew from this
project's layout and inherited the bug.

Cause

configureSheet built a fresh ConfigureSheetController on every read and stored
it in the view's only slot:

override var configureSheet: NSWindow? {
    let controller = ConfigureSheetController { … }   // new every read
    sheetController = controller                       // drops the previous one
    return controller.window
}

The window's only owner was that slot, so the second read deallocated the window
handed out by the first. Two ways that bites:

  • a host that reads the property twice — once to decide, once to present — is left
    presenting a deallocated window
  • a host that replaces the ScreenSaverView between reading the property and
    presenting it loses the window along with the old view, which is what happens
    around display reconfiguration

Either way the sheet never appears, and no crash report is filed — the window is
simply gone by the time it is wanted. That is what makes it intermittent.

Separately, the window was left with isReleasedWhenClosed at its default of
true while ARC owned it through the controller: an over-release waiting for a
host that closes the sheet rather than ordering it out.

Fix

One controller, built on first use and kept. reload() re-reads the saved
settings each time the sheet is handed over, so reopening shows what is stored
rather than where the last visit was left. isReleasedWhenClosed turned off.

Verified

Driving the built bundle the way a host does, before and after:

                              before                    after
read #1                       NSWindow 0x…8000          NSWindow 0x…4000
read #2                       NSWindow 0x…d180          NSWindow 0x…4000
window from read #1           DEALLOCATED               alive
isReleasedWhenClosed          true                      false

Reuse does not leak edits — move a slider, cancel, reopen:

open 1: first slider = 12.0
open 1: moved to 25.0
same window reused: true
open 2: first slider = 12.0 (stored value — cancel was honoured)

Three present/dismiss rounds stay clean (attachedSheet back to nil each time).
Both schemes build and analyse; swiftlint --strict, swiftlint analyze --strict
and swiftformat --lint are clean.

What this does not prove is that the symptom is gone in practice — it is
intermittent, and the harness can only show that the mechanism behind it is. That
needs real use in System Settings.

`configureSheet` built a fresh ConfigureSheetController on every read and stored
it in the view's only slot, so the second read dropped the last owner of the
window handed out by the first. A host that reads the property twice — or that
replaces the ScreenSaverView between reading it and presenting it, which happens
on display reconfiguration — is left presenting a deallocated window, and
Options appears to do nothing.

Exercising the built bundle the way a host does confirms it: reading the
property twice reported the first window DEALLOCATED, and now reports the same
pointer, alive.

The controller is now built once and kept, and reload() re-reads the saved
settings each time the sheet is handed over, so reopening shows what is stored
rather than where the last visit was left. Verified by moving a slider,
cancelling, and reopening: it comes back at its stored value.

The window also had isReleasedWhenClosed left at its default of true while ARC
owned it through the controller, which is an over-release waiting for a host
that closes the sheet rather than ordering it out. Turned off.

Found while chasing the same symptom in winebarrel/NecoSaver, which grew from
this project's layout and inherited the bug.
@winebarrel
winebarrel merged commit ef392c4 into main Aug 10, 2026
3 checks passed
@winebarrel
winebarrel deleted the fix-configure-sheet-lifetime branch August 10, 2026 04:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant