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:
@@ -95,11 +95,9 @@ struct DayView: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// Cached sorted year/month data to avoid sorting dictionaries in ForEach
|
||||
/// Sorted year/month data cached in ViewModel — avoids re-sorting on every render
|
||||
private var sortedGroupedData: [(year: Int, months: [(month: Int, entries: [MoodEntryModel])])] {
|
||||
viewModel.grouped
|
||||
.sorted { $0.key > $1.key }
|
||||
.map { (year: $0.key, months: $0.value.sorted { $0.key > $1.key }.map { (month: $0.key, entries: $0.value) }) }
|
||||
viewModel.sortedGroupedData
|
||||
}
|
||||
|
||||
private var listView: some View {
|
||||
@@ -313,6 +311,7 @@ extension DayView {
|
||||
}
|
||||
}
|
||||
)
|
||||
.accessibilityIgnoresInvertColors(true)
|
||||
}
|
||||
|
||||
private func inkSectionHeader(month: Int, year: Int) -> some View {
|
||||
|
||||
@@ -12,6 +12,7 @@ import SwiftData
|
||||
class DayViewViewModel: ObservableObject {
|
||||
@Published var grouped = [Int: [Int: [MoodEntryModel]]]()
|
||||
@Published var numberOfItems = 0
|
||||
@Published var sortedGroupedData: [(year: Int, months: [(month: Int, entries: [MoodEntryModel])])] = []
|
||||
|
||||
let addMonthStartWeekdayPadding: Bool
|
||||
|
||||
@@ -28,10 +29,12 @@ class DayViewViewModel: ObservableObject {
|
||||
grouped.values.flatMap(\.values).reduce(0) { $0 + $1.count }
|
||||
}
|
||||
|
||||
private var dataListenerToken: DataController.DataListenerToken?
|
||||
|
||||
init(addMonthStartWeekdayPadding: Bool) {
|
||||
self.addMonthStartWeekdayPadding = addMonthStartWeekdayPadding
|
||||
|
||||
DataController.shared.addNewDataListener { [weak self] in
|
||||
dataListenerToken = DataController.shared.addNewDataListener { [weak self] in
|
||||
guard let self = self else { return }
|
||||
// Avoid withAnimation for bulk data updates - it causes expensive view diffing
|
||||
self.updateData()
|
||||
@@ -39,6 +42,14 @@ class DayViewViewModel: ObservableObject {
|
||||
updateData()
|
||||
}
|
||||
|
||||
deinit {
|
||||
if let token = dataListenerToken {
|
||||
Task { @MainActor in
|
||||
DataController.shared.removeDataListener(token: token)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func getGroupedData(addMonthStartWeekdayPadding: Bool) {
|
||||
var newStuff = DataController.shared.splitIntoYearMonth(includedDays: [1,2,3,4,5,6,7])
|
||||
if addMonthStartWeekdayPadding {
|
||||
@@ -50,6 +61,9 @@ class DayViewViewModel: ObservableObject {
|
||||
|
||||
public func updateData() {
|
||||
getGroupedData(addMonthStartWeekdayPadding: self.addMonthStartWeekdayPadding)
|
||||
sortedGroupedData = grouped
|
||||
.sorted { $0.key > $1.key }
|
||||
.map { (year: $0.key, months: $0.value.sorted { $0.key > $1.key }.map { (month: $0.key, entries: $0.value) }) }
|
||||
}
|
||||
|
||||
public func add(mood: Mood, forDate date: Date, entryType: EntryType) {
|
||||
|
||||
Reference in New Issue
Block a user