Skip to content

SwiftUI Performance Audit - Corrected FindingsΒ #3617

@alltheseas

Description

@alltheseas

SwiftUI Performance Audit - Corrected Findings

⚠️ Profile-Driven Analysis with Test Requirements

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:22 relies on make_actionbar_model Task
  • SelectedEventView.swift:32 gates UI on bar initialization
  • Fix MUST NOT break these dependencies

Requirements:

  • Add hasBeenUpdated flag 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:

  1. Instruments trace showing the issue exists
  2. Failing test demonstrating the problem
  3. Fix that makes the test pass
  4. Instruments trace showing improvement
  5. 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
  • .beads/SwiftUI_Performance_Audit_Report.md - Original (SUPERSEDED, contains errors)

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

  1. Start with P0: Fix ProfileView infinite loop (damus-1gh)
  2. Validate with tools: Instruments + Thread Sanitizer
  3. Test-driven: Failing test β†’ Fix β†’ Passing test
  4. Measure impact: Before/after FPS, Task counts, memory

Bottom Line: Profile-driven, test-verified fixes only. No assumptions.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions