Memory Leak - NotificationCenter observers never removed in WhisperState
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
-
Use the app normally for an extended period with multiple recordings
-
Monitor memory usage in Activity Monitor
-
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:
-
Monitor memory usage during normal operation
-
Verify memory is reclaimed during idle periods
-
Confirm no crashes or unexpected behavior during app lifecycle