Fix 25 audit issues: memory leaks, concurrency, performance, accessibility
Address findings from comprehensive audit across 5 workstreams: - Memory: Token-based DataController listeners (prevent closure leaks), static DateFormatters, ImageCache observer cleanup, MotionManager reference counting, FoundationModels dedup guard - Concurrency: Replace Task.detached with Task in FeelsApp (preserve MainActor isolation), wrap WatchConnectivity handler in MainActor - Performance: Cache sortedGroupedData in DayViewViewModel, cache demo data in MonthView/YearView, remove broken ReduceMotionModifier - Accessibility: VoiceOver support for LockScreen, DemoHeatmapCell labels, MonthCard button labels, InsightsView header traits, Smart Invert protection on neon headers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -22,7 +22,8 @@ final class DataController: ObservableObject {
|
||||
|
||||
|
||||
// Listeners for data changes (keeping existing pattern)
|
||||
private var editedDataClosure = [() -> Void]()
|
||||
typealias DataListenerToken = UUID
|
||||
private var dataListeners: [DataListenerToken: () -> Void] = [:]
|
||||
|
||||
// Computed properties for earliest/latest entries
|
||||
var earliestEntry: MoodEntryModel? {
|
||||
@@ -48,15 +49,22 @@ final class DataController: ObservableObject {
|
||||
|
||||
// MARK: - Listener Management
|
||||
|
||||
func addNewDataListener(closure: @escaping (() -> Void)) {
|
||||
editedDataClosure.append(closure)
|
||||
@discardableResult
|
||||
func addNewDataListener(closure: @escaping (() -> Void)) -> DataListenerToken {
|
||||
let token = DataListenerToken()
|
||||
dataListeners[token] = closure
|
||||
return token
|
||||
}
|
||||
|
||||
func removeDataListener(token: DataListenerToken) {
|
||||
dataListeners.removeValue(forKey: token)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func saveAndRunDataListeners() -> Bool {
|
||||
let success = save()
|
||||
if success {
|
||||
for closure in editedDataClosure {
|
||||
for closure in dataListeners.values {
|
||||
closure()
|
||||
}
|
||||
}
|
||||
@@ -91,7 +99,7 @@ final class DataController: ObservableObject {
|
||||
modelContext.rollback()
|
||||
|
||||
// Notify listeners to re-fetch their data
|
||||
for closure in editedDataClosure {
|
||||
for closure in dataListeners.values {
|
||||
closure()
|
||||
}
|
||||
|
||||
|
||||
@@ -77,8 +77,12 @@ protocol MoodDataPersisting {
|
||||
@discardableResult
|
||||
func saveAndRunDataListeners() -> Bool
|
||||
|
||||
/// Add a listener for data changes
|
||||
func addNewDataListener(closure: @escaping (() -> Void))
|
||||
/// Add a listener for data changes, returns a token for removal
|
||||
@discardableResult
|
||||
func addNewDataListener(closure: @escaping (() -> Void)) -> DataController.DataListenerToken
|
||||
|
||||
/// Remove a previously registered data listener
|
||||
func removeDataListener(token: DataController.DataListenerToken)
|
||||
}
|
||||
|
||||
/// Combined protocol for full data controller functionality
|
||||
|
||||
Reference in New Issue
Block a user