@@ -19,57 +19,83 @@ struct MenuBarView: View {
1919 @State private var expandedProcesses : Set < Int > = [ ]
2020 @Default ( . useTreeView) private var useTreeView
2121 @Default ( . hideSystemProcesses) private var hideSystemProcesses
22+
23+ // MARK: - Cached Data (Memory Optimization)
24+ @State private var cachedFilteredPorts : [ PortInfo ] = [ ]
2225 @State private var cachedGroups : [ ProcessGroup ] = [ ]
23- @State private var groupingTrigger = 0
26+ @State private var lastCacheKey : CacheKey ?
27+
28+ /// Cache key to detect when recalculation is needed
29+ private struct CacheKey : Equatable {
30+ let portsCount : Int
31+ let firstPortHash : Int
32+ let searchText : String
33+ let hideSystem : Bool
34+ }
2435
2536 private var groupedByProcess : [ ProcessGroup ] { cachedGroups }
2637
27- /// Updates cached process groups from filtered ports
28- private func updateGroupedByProcess( ) {
29- let grouped = Dictionary ( grouping: filteredPorts) { $0. pid }
38+ /// Updates all cached data only when inputs change
39+ private func updateCachedData( ) {
40+ let currentKey = CacheKey (
41+ portsCount: state. ports. count,
42+ firstPortHash: state. ports. first? . hashValue ?? 0 ,
43+ searchText: searchText,
44+ hideSystem: hideSystemProcesses
45+ )
46+
47+ // Skip if nothing changed
48+ guard currentKey != lastCacheKey else { return }
49+ lastCacheKey = currentKey
50+
51+ // Compute filtered ports once
52+ var filtered : [ PortInfo ]
53+ if searchText. isEmpty {
54+ filtered = state. ports
55+ } else {
56+ filtered = state. ports. filter {
57+ String ( $0. port) . contains ( searchText) || $0. processName. localizedCaseInsensitiveContains ( searchText)
58+ }
59+ }
60+
61+ if hideSystemProcesses {
62+ filtered = filtered. filter { $0. processType != . system }
63+ }
64+
65+ cachedFilteredPorts = filtered. sorted { a, b in
66+ let aFav = state. isFavorite ( a. port)
67+ let bFav = state. isFavorite ( b. port)
68+ if aFav != bFav { return aFav }
69+ return a. port < b. port
70+ }
71+
72+ // Compute groups from cached filtered ports
73+ let grouped = Dictionary ( grouping: cachedFilteredPorts) { $0. pid }
3074 cachedGroups = grouped. map { pid, ports in
3175 ProcessGroup (
3276 id: pid,
3377 processName: ports. first? . processName ?? " Unknown " ,
3478 ports: ports. sorted { $0. port < $1. port }
3579 )
3680 } . sorted { a, b in
37- // Check if groups have favorite or watched ports
3881 let aHasFavorite = a. ports. contains ( where: { state. isFavorite ( $0. port) } )
3982 let aHasWatched = a. ports. contains ( where: { state. isWatching ( $0. port) } )
4083 let bHasFavorite = b. ports. contains ( where: { state. isFavorite ( $0. port) } )
4184 let bHasWatched = b. ports. contains ( where: { state. isWatching ( $0. port) } )
4285
43- // Priority: Favorite > Watched > Neither
4486 let aPriority = aHasFavorite ? 2 : ( aHasWatched ? 1 : 0 )
4587 let bPriority = bHasFavorite ? 2 : ( bHasWatched ? 1 : 0 )
4688
4789 if aPriority != bPriority {
4890 return aPriority > bPriority
4991 } else {
50- // Same priority, sort alphabetically by process name
5192 return a. processName. localizedCaseInsensitiveCompare ( b. processName) == . orderedAscending
5293 }
5394 }
5495 }
5596
56- /// Filters ports based on search text and sorts by favorites
57- private var filteredPorts : [ PortInfo ] {
58- var filtered = searchText. isEmpty ? state. ports : state. ports. filter {
59- String ( $0. port) . contains ( searchText) || $0. processName. localizedCaseInsensitiveContains ( searchText)
60- }
61-
62- if hideSystemProcesses {
63- filtered = filtered. filter { $0. processType != . system }
64- }
65-
66- return filtered. sorted { a, b in
67- let aFav = state. isFavorite ( a. port)
68- let bFav = state. isFavorite ( b. port)
69- if aFav != bFav { return aFav }
70- return a. port < b. port
71- }
72- }
97+ /// Cached filtered ports (no allocation on access)
98+ private var filteredPorts : [ PortInfo ] { cachedFilteredPorts }
7399
74100 /// Filters port-forward connections based on search text
75101 private var filteredPortForwardConnections : [ PortForwardConnectionState ] {
@@ -109,14 +135,9 @@ struct MenuBarView: View {
109135 )
110136 }
111137 . frame ( width: 340 )
112- . onAppear { groupingTrigger += 1 }
113- . onChange ( of: state. ports) { _, _ in groupingTrigger += 1 }
114- . onChange ( of: searchText) { _, _ in groupingTrigger += 1 }
115- . onChange ( of: hideSystemProcesses) { _, _ in groupingTrigger += 1 }
116- . task ( id: groupingTrigger) {
117- // Debounce rapid changes to avoid excessive CPU/memory churn
118- try ? await Task . sleep ( for: . milliseconds( 100 ) )
119- updateGroupedByProcess ( )
120- }
138+ . onAppear { updateCachedData ( ) }
139+ . onChange ( of: state. ports) { _, _ in updateCachedData ( ) }
140+ . onChange ( of: searchText) { _, _ in updateCachedData ( ) }
141+ . onChange ( of: hideSystemProcesses) { _, _ in updateCachedData ( ) }
121142 }
122143}
0 commit comments