-
Notifications
You must be signed in to change notification settings - Fork 302
Description
SwiftUI Performance Audit - Corrected Findings
This audit identified 5 confirmed SwiftUI performance issues through code analysis. All issues require Instruments profiling + failing tests before implementation.
π‘ P1 - High Priority (3 issues)
2. Duplicate action bar task creation
Issue: damus-obn
Files: EventView.swift:116 + EventActionBar.swift:256
// EventView.swift:116 - Task #1
func make_actionbar_model(ev: NoteId, damus: DamusState) -> ActionBarModel {
let model = ActionBarModel.empty()
Task { await model.update(damus: damus, evid: ev) }
return model
}
// EventActionBar.swift:256 - Task #2 (duplicate!)
.onAppear {
Task.detached(priority: .background) {
await self.bar.update(damus: damus_state, evid: self.event.id)
}
}Impact: Every timeline event creates 2 Tasks doing the same work (100 events = 200 Tasks).
Critical Dependencies:
EventDetailBar.swift:22relies on make_actionbar_model TaskSelectedEventView.swift:32gates UI on bar initialization- Fix MUST NOT break these dependencies
Requirements:
- Add
hasBeenUpdatedflag to ActionBarModel - Guard duplicate update in onAppear
- Test EventDetailBar still works
- Instruments showing reduced Task count
3. NotificationsView: DispatchQueue.main.async cleanup
Issue: damus-9dv
File: NotificationsView.swift:185-190
.background(GeometryReader { proxy -> Color in
DispatchQueue.main.async {
handle_scroll_queue(proxy, queue: self.notifications)
}
return Color.clear
})Impact: Anti-pattern (mutates non-@published property), not infinite loop. Code health improvement.
Requirements:
- Refactor to PreferenceKey or onChange
- Test no functional regression
- Verify performance unchanged
4. EventActionBar: @State mutation from detached task
Issue: damus-fa7
Files: EventActionBar.swift:240-242, 257
@State var event_relay_url_strings: [RelayURL] = []
func updateEventRelayURLStrings() async {
let newValue = await fetchEventRelayURLStrings()
self.event_relay_url_strings = newValue // β @State from background
}
.onAppear {
Task.detached(priority: .background) {
await self.updateEventRelayURLStrings()
}
}Impact: Thread safety violation. @State MUST be updated on MainActor.
Note: lnurl property (line 45) is SAFE - correctly uses DispatchQueue.main.async.
Requirements:
- Thread Sanitizer failing test
- Fix with @mainactor or MainActor.run
- Thread Sanitizer passing
- Instruments validation
π’ P2 - Medium Priority (1 issue)
5. DateFormatter in detail views
Issue: damus-r56
Files: SelectedEventView.swift:61, IAPProductStateView.swift:89,96
func format_date(created_at: UInt32) -> String {
let dateFormatter = DateFormatter() // Created every call
dateFormatter.timeStyle = time_style
dateFormatter.dateStyle = .short
return dateFormatter.string(from: date)
}Impact: DateFormatter creation is expensive. Only affects detail views (NOT timeline rendering).
Requirements:
- Instruments showing DateFormatter cost
- Create static cached formatters
- Instruments showing improvement
π Hard Requirements for ALL Fixes
Every PR MUST include:
- Instruments trace showing the issue exists
- Failing test demonstrating the problem
- Fix that makes the test pass
- Instruments trace showing improvement
- No functional regressions
Test + Instruments validation = 99% merge rate
π Reference Documents
Correction History:
.beads/AUDIT_CORRECTIONS_v3.md- Complete error analysis (16 errors across 3 rounds).beads/AUDIT_CORRECTIONS_v2.md- Round 2 corrections.beads/AUDIT_CORRECTIONS.md- Round 1 corrections- Original (SUPERSEDED, contains errors).beads/SwiftUI_Performance_Audit_Report.md
Key Lessons:
- β Profile with Instruments FIRST
- β Trace actual call sites
- β Verify @published status for render claims
- β Check ALL dependencies before suggesting changes
- β Test assertions must work for edge cases
- β Don't assume - counts β bottlenecks without data
π― Next Steps
- Start with P0: Fix ProfileView infinite loop (damus-1gh)
- Validate with tools: Instruments + Thread Sanitizer
- Test-driven: Failing test β Fix β Passing test
- Measure impact: Before/after FPS, Task counts, memory
Bottom Line: Profile-driven, test-verified fixes only. No assumptions.