VoiceInk icon indicating copy to clipboard operation
VoiceInk copied to clipboard

Memory Leak - NotificationCenter observers never removed in WhisperState

Open ebrindley opened this issue 1 month ago • 0 comments

Memory Leak - NotificationCenter observers never removed in WhisperState

Labels: bug, memory-leak, high-priority

Description

WhisperState registers 4 NotificationCenter observers during initialization but never removes them, creating a retain cycle that prevents the object from being deallocated.

User Impact

  • Memory accumulates over app lifetime causing gradual performance degradation

  • Increased memory pressure on systems with limited RAM

  • Users may need to restart the app periodically to reclaim memory

  • Noticeable slowdown after multiple recording sessions

Technical Details

File: VoiceInk/Whisper/WhisperState+UI.swift

Lines: 114-119

The setupNotifications() method adds 4 observers:


func setupNotifications() {

    NotificationCenter.default.addObserver(self, selector: #selector(handleToggleMiniRecorder), name: .toggleMiniRecorder, object: nil)

    NotificationCenter.default.addObserver(self, selector: #selector(handleDismissMiniRecorder), name: .dismissMiniRecorder, object: nil)

    NotificationCenter.default.addObserver(self, selector: #selector(handleLicenseStatusChanged), name: .licenseStatusChanged, object: nil)

    NotificationCenter.default.addObserver(self, selector: #selector(handlePromptChange), name: .promptDidChange, object: nil)

}

This method is called from WhisperState.init() (line 129 in WhisperState.swift), but there is no corresponding deinit to remove these observers. NotificationCenter holds a strong reference to self, preventing deallocation.

Reproduction

  1. Use the app normally for an extended period with multiple recordings

  2. Monitor memory usage in Activity Monitor

  3. Observe memory never being reclaimed even when app is idle

Recommended Fix

Add a deinit method to VoiceInk/Whisper/WhisperState.swift after line 445:


deinit {

    NotificationCenter.default.removeObserver(self)

}

This ensures all observers are properly cleaned up when WhisperState is deallocated.

Testing

After applying the fix:

  1. Monitor memory usage during normal operation

  2. Verify memory is reclaimed during idle periods

  3. Confirm no crashes or unexpected behavior during app lifecycle

ebrindley avatar Nov 12 '25 18:11 ebrindley