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-04-15 - Optimizing Published Arrays in ViewModels
**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. This can be especially inefficient if the array has many elements, as each update forces SwiftUI to recalculate the view hierarchy repeatedly.
**Action:** For collections of value types (structs), utilize functional methods like `.map` to batch updates into a single property assignment, significantly reducing unnecessary UI recalculations. Always add comments explaining this optimization to prevent future developers from 'optimizing' it back to a standard `for` loop, as `.map` can appear slower in standard Swift contexts.
43 changes: 35 additions & 8 deletions Sources/Cacheout/ViewModels/CacheoutViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -172,14 +172,26 @@ class CacheoutViewModel: ObservableObject {
}

func selectAllSafe() {
for i in scanResults.indices where scanResults[i].category.riskLevel == .safe && !scanResults[i].isEmpty {
scanResults[i].isSelected = true
// Optimization: Use .map to batch array updates instead of a loop.
// Mutating individual elements of a @Published array inside a loop
// triggers a UI update for every single change. This reduces it to one.
scanResults = scanResults.map { result in
var r = result
if r.category.riskLevel == .safe && !r.isEmpty {
r.isSelected = true
}
return r
}
}

func deselectAll() {
for i in scanResults.indices {
scanResults[i].isSelected = false
// Optimization: Use .map to batch array updates instead of a loop.
// Mutating individual elements of a @Published array inside a loop
// triggers a UI update for every single change. This reduces it to one.
scanResults = scanResults.map { result in
var r = result
r.isSelected = false
return r
}
deselectAllNodeModules()
}
Expand All @@ -193,17 +205,32 @@ class CacheoutViewModel: ObservableObject {
}

func selectStaleNodeModules() {
for i in nodeModulesItems.indices where nodeModulesItems[i].isStale {
nodeModulesItems[i].isSelected = true
// Optimization: Use .map to batch array updates instead of a loop.
nodeModulesItems = nodeModulesItems.map { item in
var m = item
if m.isStale {
m.isSelected = true
}
return m
}
}

func selectAllNodeModules() {
for i in nodeModulesItems.indices { nodeModulesItems[i].isSelected = true }
// Optimization: Use .map to batch array updates instead of a loop.
nodeModulesItems = nodeModulesItems.map { item in
var m = item
m.isSelected = true
return m
}
}

func deselectAllNodeModules() {
for i in nodeModulesItems.indices { nodeModulesItems[i].isSelected = false }
// Optimization: Use .map to batch array updates instead of a loop.
nodeModulesItems = nodeModulesItems.map { item in
var m = item
m.isSelected = false
return m
}
}

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