Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2024-05-13 - Batching @Published Array Mutations
**Learning:** In SwiftUI ObservableObject view models, mutating individual elements of a @Published array property inside a loop triggers a UI update notification for every change. For collections of value types (structs), functional methods like map batch updates into a single property assignment, significantly reducing unnecessary UI recalculations.
**Action:** Replace for loop mutations on @Published array collections with .map re-assignments to batch updates, and document this with comments to prevent future reversions.
39 changes: 31 additions & 8 deletions Sources/Cacheout/ViewModels/CacheoutViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -172,14 +172,22 @@ class CacheoutViewModel: ObservableObject {
}

func selectAllSafe() {
for i in scanResults.indices where scanResults[i].category.riskLevel == .safe && !scanResults[i].isEmpty {
scanResults[i].isSelected = true
// Optimized: Use .map to batch updates into a single @Published assignment, avoiding O(N) UI recalculations
scanResults = scanResults.map { result in
var modified = result
if modified.category.riskLevel == .safe && !modified.isEmpty {
modified.isSelected = true
}
return modified
}
}

func deselectAll() {
for i in scanResults.indices {
scanResults[i].isSelected = false
// Optimized: Use .map to batch updates into a single @Published assignment, avoiding O(N) UI recalculations
scanResults = scanResults.map { result in
var modified = result
modified.isSelected = false
return modified
}
deselectAllNodeModules()
}
Expand All @@ -193,17 +201,32 @@ class CacheoutViewModel: ObservableObject {
}

func selectStaleNodeModules() {
for i in nodeModulesItems.indices where nodeModulesItems[i].isStale {
nodeModulesItems[i].isSelected = true
// Optimized: Use .map to batch updates into a single @Published assignment, avoiding O(N) UI recalculations
nodeModulesItems = nodeModulesItems.map { item in
var modified = item
if modified.isStale {
modified.isSelected = true
}
return modified
}
}

func selectAllNodeModules() {
for i in nodeModulesItems.indices { nodeModulesItems[i].isSelected = true }
// Optimized: Use .map to batch updates into a single @Published assignment, avoiding O(N) UI recalculations
nodeModulesItems = nodeModulesItems.map { item in
var modified = item
modified.isSelected = true
return modified
}
}

func deselectAllNodeModules() {
for i in nodeModulesItems.indices { nodeModulesItems[i].isSelected = false }
// Optimized: Use .map to batch updates into a single @Published assignment, avoiding O(N) UI recalculations
nodeModulesItems = nodeModulesItems.map { item in
var modified = item
modified.isSelected = false
return modified
}
}

/// Menu bar label: show free GB in the tray
Expand Down