swift-code-reviewer-skill 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,914 @@
1
+ # SwiftUI Performance Review Guide
2
+
3
+ This guide covers common performance anti-patterns in SwiftUI, view update optimization, ForEach identity issues, layout performance, and resource management. Use this to identify and fix performance problems in SwiftUI code.
4
+
5
+ ---
6
+
7
+ ## 1. View Update Optimization
8
+
9
+ ### 1.1 Unnecessary View Updates
10
+
11
+ **Check for:**
12
+ - [ ] Views re-rendering when data hasn't changed
13
+ - [ ] Excessive @State or @Observable properties
14
+ - [ ] Parent updates causing child re-renders unnecessarily
15
+
16
+ **Common Causes:**
17
+ - Non-Equatable view models
18
+ - Computed properties that always return new values
19
+ - Reference type mutations triggering updates
20
+ - Closures capturing mutable state
21
+
22
+ **Examples:**
23
+
24
+ ❌ **Bad: Excessive updates**
25
+ ```swift
26
+ @Observable
27
+ final class ViewModel {
28
+ var timestamp: Date { // ❌ New value every access
29
+ Date()
30
+ }
31
+ }
32
+
33
+ struct ContentView: View {
34
+ let viewModel: ViewModel
35
+
36
+ var body: some View {
37
+ Text("Current time: \(viewModel.timestamp)") // ❌ Updates constantly
38
+ }
39
+ }
40
+ ```
41
+
42
+ ✅ **Good: Controlled updates**
43
+ ```swift
44
+ @Observable
45
+ final class ViewModel {
46
+ private(set) var timestamp: Date = Date()
47
+
48
+ func updateTimestamp() {
49
+ timestamp = Date() // ✅ Explicit update only
50
+ }
51
+ }
52
+ ```
53
+
54
+ ### 1.2 Heavy Computation in Body
55
+
56
+ **Check for:**
57
+ - [ ] Sorting, filtering, or mapping in body
58
+ - [ ] Network calls or database queries in body
59
+ - [ ] Complex calculations during render
60
+
61
+ **Examples:**
62
+
63
+ ❌ **Bad: Computation in body**
64
+ ```swift
65
+ struct ItemListView: View {
66
+ let items: [Item]
67
+
68
+ var body: some View {
69
+ let filtered = items.filter { $0.isActive } // ❌ Every render
70
+ let sorted = filtered.sorted { $0.date > $1.date } // ❌ Every render
71
+
72
+ List(sorted) { item in
73
+ ItemRow(item: item)
74
+ }
75
+ }
76
+ }
77
+ ```
78
+
79
+ ✅ **Good: Computed property**
80
+ ```swift
81
+ struct ItemListView: View {
82
+ let items: [Item]
83
+
84
+ private var processedItems: [Item] { // ✅ Computed property
85
+ items
86
+ .filter { $0.isActive }
87
+ .sorted { $0.date > $1.date }
88
+ }
89
+
90
+ var body: some View {
91
+ List(processedItems) { item in
92
+ ItemRow(item: item)
93
+ }
94
+ }
95
+ }
96
+ ```
97
+
98
+ ✅ **Better: View model handles logic**
99
+ ```swift
100
+ @Observable
101
+ final class ItemListViewModel {
102
+ var items: [Item] = []
103
+ var showActiveOnly: Bool = true
104
+
105
+ var displayedItems: [Item] { // ✅ Cached by view model
106
+ let filtered = showActiveOnly ? items.filter { $0.isActive } : items
107
+ return filtered.sorted { $0.date > $1.date }
108
+ }
109
+ }
110
+
111
+ struct ItemListView: View {
112
+ let viewModel: ItemListViewModel
113
+
114
+ var body: some View {
115
+ List(viewModel.displayedItems) { item in // ✅ Already processed
116
+ ItemRow(item: item)
117
+ }
118
+ }
119
+ }
120
+ ```
121
+
122
+ ### 1.3 Equatable Conformance
123
+
124
+ **Check for:**
125
+ - [ ] View models conform to Equatable
126
+ - [ ] Views use .equatable() modifier
127
+ - [ ] Proper equality implementation
128
+
129
+ **Examples:**
130
+
131
+ ✅ **Good: Equatable view model**
132
+ ```swift
133
+ @Observable
134
+ final class ItemViewModel: Equatable {
135
+ let id: UUID
136
+ var title: String
137
+ var subtitle: String
138
+
139
+ static func == (lhs: ItemViewModel, rhs: ItemViewModel) -> Bool {
140
+ lhs.id == rhs.id &&
141
+ lhs.title == rhs.title &&
142
+ lhs.subtitle == rhs.subtitle
143
+ }
144
+ }
145
+
146
+ struct ItemRow: View {
147
+ let viewModel: ItemViewModel
148
+
149
+ var body: some View {
150
+ VStack(alignment: .leading) {
151
+ Text(viewModel.title)
152
+ Text(viewModel.subtitle)
153
+ }
154
+ }
155
+ .equatable() // ✅ Only updates when viewModel changes
156
+ }
157
+ ```
158
+
159
+ ### 1.4 Avoid Struct Copying
160
+
161
+ **Check for:**
162
+ - [ ] Large structs being copied frequently
163
+ - [ ] Reference semantics where appropriate
164
+ - [ ] Efficient data structures
165
+
166
+ **Examples:**
167
+
168
+ ❌ **Bad: Large struct copying**
169
+ ```swift
170
+ struct LargeData {
171
+ let items: [Item] // Large array
172
+ let metadata: [String: Any] // Large dictionary
173
+ // ... more properties
174
+
175
+ func withUpdatedItem(_ item: Item) -> LargeData { // ❌ Copies entire struct
176
+ var copy = self
177
+ // Update logic
178
+ return copy
179
+ }
180
+ }
181
+ ```
182
+
183
+ ✅ **Good: Reference type for mutable state**
184
+ ```swift
185
+ @Observable
186
+ final class LargeDataModel { // ✅ Reference type
187
+ var items: [Item] = []
188
+ var metadata: [String: Any] = [:]
189
+
190
+ func updateItem(_ item: Item) { // ✅ No copying
191
+ // Update in place
192
+ }
193
+ }
194
+ ```
195
+
196
+ ---
197
+
198
+ ## 2. ForEach Performance
199
+
200
+ ### 2.1 Stable Identity
201
+
202
+ **Check for:**
203
+ - [ ] ForEach uses stable IDs (Identifiable or explicit id)
204
+ - [ ] No index-based iteration when data changes
205
+ - [ ] No array.indices or enumerated() in ForEach
206
+
207
+ **Examples:**
208
+
209
+ ❌ **Bad: Index-based identity**
210
+ ```swift
211
+ List {
212
+ ForEach(items.indices, id: \.self) { index in // ❌ Unstable identity
213
+ ItemRow(item: items[index])
214
+ }
215
+ }
216
+ ```
217
+
218
+ ❌ **Bad: Enumerated**
219
+ ```swift
220
+ ForEach(Array(items.enumerated()), id: \.offset) { index, item in // ❌ Unstable
221
+ ItemRow(item: item)
222
+ }
223
+ ```
224
+
225
+ ✅ **Good: Identifiable**
226
+ ```swift
227
+ struct Item: Identifiable {
228
+ let id: UUID
229
+ let title: String
230
+ }
231
+
232
+ List {
233
+ ForEach(items) { item in // ✅ Using Identifiable
234
+ ItemRow(item: item)
235
+ }
236
+ }
237
+ ```
238
+
239
+ ✅ **Good: Explicit stable ID**
240
+ ```swift
241
+ struct Item {
242
+ let id: UUID
243
+ let title: String
244
+ }
245
+
246
+ List {
247
+ ForEach(items, id: \.id) { item in // ✅ Explicit stable ID
248
+ ItemRow(item: item)
249
+ }
250
+ }
251
+ ```
252
+
253
+ ### 2.2 ForEach Identity for Animations
254
+
255
+ **Check for:**
256
+ - [ ] Stable IDs for smooth animations
257
+ - [ ] ID includes all relevant data for transitions
258
+ - [ ] No changing IDs during animations
259
+
260
+ **Examples:**
261
+
262
+ ❌ **Bad: Changing ID during animation**
263
+ ```swift
264
+ ForEach(items, id: \.timestamp) { item in // ❌ Timestamp changes
265
+ ItemRow(item: item)
266
+ }
267
+ .animation(.default, value: items)
268
+ ```
269
+
270
+ ✅ **Good: Stable ID for animations**
271
+ ```swift
272
+ ForEach(items, id: \.id) { item in // ✅ ID never changes
273
+ ItemRow(item: item)
274
+ }
275
+ .animation(.default, value: items)
276
+ ```
277
+
278
+ ### 2.3 Large List Performance
279
+
280
+ **Check for:**
281
+ - [ ] LazyVStack/LazyHStack for large lists
282
+ - [ ] Lazy loading for off-screen items
283
+ - [ ] Pagination for very large datasets
284
+
285
+ **Examples:**
286
+
287
+ ❌ **Bad: Non-lazy stack for large list**
288
+ ```swift
289
+ ScrollView {
290
+ VStack { // ❌ All views created immediately
291
+ ForEach(1000..<10000) { index in
292
+ HeavyView(index: index)
293
+ }
294
+ }
295
+ }
296
+ ```
297
+
298
+ ✅ **Good: Lazy stack**
299
+ ```swift
300
+ ScrollView {
301
+ LazyVStack { // ✅ Views created on-demand
302
+ ForEach(1000..<10000) { index in
303
+ HeavyView(index: index)
304
+ }
305
+ }
306
+ }
307
+ ```
308
+
309
+ ✅ **Good: List (built-in lazy loading)**
310
+ ```swift
311
+ List(items) { item in // ✅ List is lazy by default
312
+ ItemRow(item: item)
313
+ }
314
+ ```
315
+
316
+ ### 2.4 Cell Reuse Patterns
317
+
318
+ **Check for:**
319
+ - [ ] Minimal state in rows
320
+ - [ ] No heavy initialization in row views
321
+ - [ ] Efficient data passing
322
+
323
+ **Examples:**
324
+
325
+ ❌ **Bad: Heavy initialization in row**
326
+ ```swift
327
+ struct ItemRow: View {
328
+ let item: Item
329
+
330
+ var body: some View {
331
+ let processedData = heavyProcessing(item) // ❌ Every render
332
+
333
+ VStack {
334
+ Text(processedData.title)
335
+ Text(processedData.subtitle)
336
+ }
337
+ }
338
+
339
+ private func heavyProcessing(_ item: Item) -> ProcessedData {
340
+ // Expensive operation
341
+ }
342
+ }
343
+ ```
344
+
345
+ ✅ **Good: Pre-processed data**
346
+ ```swift
347
+ struct ItemRow: View {
348
+ let displayData: ItemDisplayData // ✅ Already processed
349
+
350
+ var body: some View {
351
+ VStack {
352
+ Text(displayData.title)
353
+ Text(displayData.subtitle)
354
+ }
355
+ }
356
+ }
357
+
358
+ // Process data before passing to view
359
+ let displayData = items.map { heavyProcessing($0) }
360
+ List(displayData) { data in
361
+ ItemRow(displayData: data)
362
+ }
363
+ ```
364
+
365
+ ---
366
+
367
+ ## 3. Layout Performance
368
+
369
+ ### 3.1 GeometryReader Overuse
370
+
371
+ **Check for:**
372
+ - [ ] Minimal GeometryReader usage
373
+ - [ ] No nested GeometryReaders
374
+ - [ ] Use .frame(maxWidth:) instead when possible
375
+
376
+ **Examples:**
377
+
378
+ ❌ **Bad: Unnecessary GeometryReader**
379
+ ```swift
380
+ GeometryReader { geometry in // ❌ Not needed
381
+ VStack {
382
+ Text("Hello")
383
+ .frame(width: geometry.size.width)
384
+ }
385
+ }
386
+ ```
387
+
388
+ ✅ **Good: Simple frame modifier**
389
+ ```swift
390
+ VStack {
391
+ Text("Hello")
392
+ .frame(maxWidth: .infinity) // ✅ Much simpler
393
+ }
394
+ ```
395
+
396
+ ❌ **Bad: Nested GeometryReaders**
397
+ ```swift
398
+ GeometryReader { outerGeometry in
399
+ VStack {
400
+ ForEach(items) { item in
401
+ GeometryReader { innerGeometry in // ❌ Nested, performance issue
402
+ ItemView(item: item, width: innerGeometry.size.width)
403
+ }
404
+ }
405
+ }
406
+ }
407
+ ```
408
+
409
+ ✅ **Good: Single GeometryReader or layout protocol**
410
+ ```swift
411
+ // Option 1: Single GeometryReader
412
+ GeometryReader { geometry in
413
+ VStack {
414
+ ForEach(items) { item in
415
+ ItemView(item: item, width: geometry.size.width) // ✅ Reuse outer
416
+ }
417
+ }
418
+ }
419
+
420
+ // Option 2: Layout protocol (iOS 16+)
421
+ struct CustomLayout: Layout {
422
+ func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
423
+ // Custom layout logic
424
+ }
425
+
426
+ func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
427
+ // Placement logic
428
+ }
429
+ }
430
+ ```
431
+
432
+ ### 3.2 Layout Thrash
433
+
434
+ **Check for:**
435
+ - [ ] No layout changes in onAppear/task
436
+ - [ ] Stable frame sizes
437
+ - [ ] No excessive frame recalculations
438
+
439
+ **Examples:**
440
+
441
+ ❌ **Bad: Layout change in onAppear**
442
+ ```swift
443
+ struct ContentView: View {
444
+ @State private var width: CGFloat = 100
445
+
446
+ var body: some View {
447
+ Rectangle()
448
+ .frame(width: width, height: 100)
449
+ .onAppear {
450
+ width = 200 // ❌ Layout thrash on appear
451
+ }
452
+ }
453
+ }
454
+ ```
455
+
456
+ ✅ **Good: Stable initial layout**
457
+ ```swift
458
+ struct ContentView: View {
459
+ @State private var width: CGFloat = 200 // ✅ Correct from start
460
+
461
+ var body: some View {
462
+ Rectangle()
463
+ .frame(width: width, height: 100)
464
+ }
465
+ }
466
+ ```
467
+
468
+ ### 3.3 Prefer .frame Over Custom Layouts
469
+
470
+ **Check for:**
471
+ - [ ] Use built-in layout modifiers when possible
472
+ - [ ] Custom layouts only when necessary
473
+ - [ ] Efficient layout calculations
474
+
475
+ **Examples:**
476
+
477
+ ✅ **Good: Built-in layout modifiers**
478
+ ```swift
479
+ // Use built-in modifiers first
480
+ HStack(spacing: 16) {
481
+ Text("Title")
482
+ .frame(maxWidth: .infinity, alignment: .leading)
483
+ Text("Value")
484
+ }
485
+ .padding()
486
+ ```
487
+
488
+ ✅ **Good: Custom layout for complex needs**
489
+ ```swift
490
+ // Only when built-in modifiers insufficient
491
+ struct WaterfallLayout: Layout {
492
+ // Complex custom layout logic
493
+ }
494
+ ```
495
+
496
+ ---
497
+
498
+ ## 4. Image Performance
499
+
500
+ ### 4.1 AsyncImage for Remote Images
501
+
502
+ **Check for:**
503
+ - [ ] AsyncImage for remote images
504
+ - [ ] Proper placeholder and error states
505
+ - [ ] No synchronous image loading
506
+
507
+ **Examples:**
508
+
509
+ ❌ **Bad: Synchronous image loading**
510
+ ```swift
511
+ if let data = try? Data(contentsOf: imageURL), // ❌ Blocks main thread
512
+ let image = UIImage(data: data) {
513
+ Image(uiImage: image)
514
+ }
515
+ ```
516
+
517
+ ✅ **Good: AsyncImage**
518
+ ```swift
519
+ AsyncImage(url: imageURL) { phase in // ✅ Async with caching
520
+ switch phase {
521
+ case .success(let image):
522
+ image
523
+ .resizable()
524
+ .aspectRatio(contentMode: .fit)
525
+ case .failure:
526
+ Image(systemName: "photo")
527
+ .foregroundColor(.gray)
528
+ case .empty:
529
+ ProgressView()
530
+ @unknown default:
531
+ EmptyView()
532
+ }
533
+ }
534
+ ```
535
+
536
+ ### 4.2 Image Sizing and Scaling
537
+
538
+ **Check for:**
539
+ - [ ] Proper image sizing (not loading huge images for small views)
540
+ - [ ] .resizable() used appropriately
541
+ - [ ] Aspect ratio preserved
542
+
543
+ **Examples:**
544
+
545
+ ❌ **Bad: Full-size image in thumbnail**
546
+ ```swift
547
+ Image("large-photo") // ❌ 4000x3000 image for 100x100 thumbnail
548
+ .frame(width: 100, height: 100)
549
+ ```
550
+
551
+ ✅ **Good: Properly sized image**
552
+ ```swift
553
+ // Provide appropriately sized asset
554
+ Image("photo-thumbnail") // ✅ 100x100 or 200x200 for @2x
555
+ .resizable()
556
+ .frame(width: 100, height: 100)
557
+ ```
558
+
559
+ ✅ **Good: Dynamic resizing with AsyncImage**
560
+ ```swift
561
+ AsyncImage(url: thumbnailURL) { image in // ✅ Request thumbnail size
562
+ image
563
+ .resizable()
564
+ .aspectRatio(contentMode: .fill)
565
+ .frame(width: 100, height: 100)
566
+ .clipped()
567
+ } placeholder: {
568
+ ProgressView()
569
+ }
570
+ ```
571
+
572
+ ### 4.3 Image Caching
573
+
574
+ **Check for:**
575
+ - [ ] AsyncImage built-in caching leveraged
576
+ - [ ] Custom caching for specific needs
577
+ - [ ] Memory-efficient cache implementation
578
+
579
+ **Examples:**
580
+
581
+ ✅ **Good: AsyncImage caching (built-in)**
582
+ ```swift
583
+ AsyncImage(url: imageURL) // ✅ Automatically cached
584
+ ```
585
+
586
+ ✅ **Good: Custom cache for specific needs**
587
+ ```swift
588
+ actor ImageCache {
589
+ private var cache: [URL: UIImage] = [:]
590
+
591
+ func image(for url: URL) -> UIImage? {
592
+ cache[url]
593
+ }
594
+
595
+ func cache(_ image: UIImage, for url: URL) {
596
+ cache[url] = image
597
+ }
598
+
599
+ func clearCache() {
600
+ cache.removeAll()
601
+ }
602
+ }
603
+ ```
604
+
605
+ ---
606
+
607
+ ## 5. Memory Management
608
+
609
+ ### 5.1 Retain Cycles
610
+
611
+ **Check for:**
612
+ - [ ] No strong reference cycles with closures
613
+ - [ ] [weak self] in closures when necessary
614
+ - [ ] Proper capture lists
615
+
616
+ **Examples:**
617
+
618
+ ❌ **Bad: Retain cycle**
619
+ ```swift
620
+ @Observable
621
+ final class ViewModel {
622
+ var onComplete: (() -> Void)?
623
+
624
+ func setup() {
625
+ onComplete = {
626
+ self.finish() // ❌ Retain cycle
627
+ }
628
+ }
629
+
630
+ func finish() { }
631
+ }
632
+ ```
633
+
634
+ ✅ **Good: Weak self**
635
+ ```swift
636
+ @Observable
637
+ final class ViewModel {
638
+ var onComplete: (() -> Void)?
639
+
640
+ func setup() {
641
+ onComplete = { [weak self] in // ✅ Weak capture
642
+ self?.finish()
643
+ }
644
+ }
645
+
646
+ func finish() { }
647
+ }
648
+ ```
649
+
650
+ ✅ **Good: Unowned for guaranteed lifetime**
651
+ ```swift
652
+ @Observable
653
+ final class ViewModel {
654
+ let dependency: Dependency
655
+
656
+ func setup() {
657
+ dependency.onEvent = { [unowned self] in // ✅ Unowned when guaranteed
658
+ self.handleEvent()
659
+ }
660
+ }
661
+ }
662
+ ```
663
+
664
+ ### 5.2 Large Data Structures
665
+
666
+ **Check for:**
667
+ - [ ] Lazy loading for large datasets
668
+ - [ ] Pagination for network data
669
+ - [ ] Data clearing when not needed
670
+
671
+ **Examples:**
672
+
673
+ ❌ **Bad: Loading all data at once**
674
+ ```swift
675
+ @Observable
676
+ final class DataViewModel {
677
+ var allItems: [Item] = [] // ❌ Could be thousands
678
+
679
+ func loadAllData() async {
680
+ allItems = await fetchAllItems() // ❌ Load everything
681
+ }
682
+ }
683
+ ```
684
+
685
+ ✅ **Good: Pagination**
686
+ ```swift
687
+ @Observable
688
+ final class DataViewModel {
689
+ var items: [Item] = []
690
+ private var currentPage = 0
691
+ private let pageSize = 50
692
+
693
+ func loadNextPage() async {
694
+ let newItems = await fetchItems(page: currentPage, size: pageSize)
695
+ items.append(contentsOf: newItems)
696
+ currentPage += 1
697
+ }
698
+ }
699
+ ```
700
+
701
+ ### 5.3 Resource Cleanup
702
+
703
+ **Check for:**
704
+ - [ ] Resources released when view disappears
705
+ - [ ] Cancellation for async tasks
706
+ - [ ] Proper cleanup in deinit
707
+
708
+ **Examples:**
709
+
710
+ ✅ **Good: Task cancellation**
711
+ ```swift
712
+ struct ContentView: View {
713
+ @State private var data: [Item] = []
714
+
715
+ var body: some View {
716
+ List(data) { item in
717
+ ItemRow(item: item)
718
+ }
719
+ .task { // ✅ Automatically cancelled on disappear
720
+ await loadData()
721
+ }
722
+ }
723
+
724
+ private func loadData() async {
725
+ // Task automatically cancelled when view disappears
726
+ }
727
+ }
728
+ ```
729
+
730
+ ✅ **Good: Manual cleanup**
731
+ ```swift
732
+ @Observable
733
+ final class ViewModel {
734
+ private var timer: Timer?
735
+
736
+ func startTimer() {
737
+ timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
738
+ // Timer logic
739
+ }
740
+ }
741
+
742
+ deinit {
743
+ timer?.invalidate() // ✅ Cleanup
744
+ }
745
+ }
746
+ ```
747
+
748
+ ---
749
+
750
+ ## 6. Background Task Efficiency
751
+
752
+ ### 6.1 Async/Await Patterns
753
+
754
+ **Check for:**
755
+ - [ ] Proper async/await usage
756
+ - [ ] No blocking main thread
757
+ - [ ] Efficient task management
758
+
759
+ **Examples:**
760
+
761
+ ❌ **Bad: Blocking main thread**
762
+ ```swift
763
+ Button("Load") {
764
+ let data = loadData() // ❌ Synchronous, blocks UI
765
+ processData(data)
766
+ }
767
+
768
+ func loadData() -> Data {
769
+ // Long-running operation
770
+ }
771
+ ```
772
+
773
+ ✅ **Good: Async operation**
774
+ ```swift
775
+ Button("Load") {
776
+ Task { // ✅ Async task
777
+ await loadData()
778
+ }
779
+ }
780
+
781
+ func loadData() async {
782
+ // Long-running operation on background
783
+ }
784
+ ```
785
+
786
+ ### 6.2 Concurrent Operations
787
+
788
+ **Check for:**
789
+ - [ ] TaskGroup for multiple concurrent operations
790
+ - [ ] Efficient concurrency patterns
791
+ - [ ] Proper error handling
792
+
793
+ **Examples:**
794
+
795
+ ❌ **Bad: Sequential operations**
796
+ ```swift
797
+ func loadAllData() async {
798
+ let users = await fetchUsers() // ❌ Wait
799
+ let posts = await fetchPosts() // ❌ Wait
800
+ let comments = await fetchComments() // ❌ Wait
801
+ }
802
+ ```
803
+
804
+ ✅ **Good: Concurrent operations**
805
+ ```swift
806
+ func loadAllData() async {
807
+ await withTaskGroup(of: Void.self) { group in
808
+ group.addTask { await self.fetchUsers() } // ✅ Parallel
809
+ group.addTask { await self.fetchPosts() } // ✅ Parallel
810
+ group.addTask { await self.fetchComments() } // ✅ Parallel
811
+ }
812
+ }
813
+ ```
814
+
815
+ ---
816
+
817
+ ## 7. Animation Performance
818
+
819
+ ### 7.1 Efficient Animations
820
+
821
+ **Check for:**
822
+ - [ ] Animations on GPU-accelerated properties (opacity, transform)
823
+ - [ ] No animating layout changes excessively
824
+ - [ ] Proper animation curves
825
+
826
+ **Examples:**
827
+
828
+ ✅ **Good: GPU-accelerated properties**
829
+ ```swift
830
+ Rectangle()
831
+ .opacity(isVisible ? 1.0 : 0.0) // ✅ GPU-accelerated
832
+ .scaleEffect(isExpanded ? 1.2 : 1.0) // ✅ GPU-accelerated
833
+ .animation(.easeInOut, value: isVisible)
834
+ ```
835
+
836
+ ❌ **Bad: Excessive layout animations**
837
+ ```swift
838
+ VStack {
839
+ if isExpanded { // ❌ Layout changes on every animation frame
840
+ ForEach(1...100) { index in
841
+ Text("Item \(index)")
842
+ }
843
+ }
844
+ }
845
+ .animation(.default, value: isExpanded)
846
+ ```
847
+
848
+ ✅ **Good: Controlled layout animations**
849
+ ```swift
850
+ VStack {
851
+ if isExpanded {
852
+ ForEach(1...10) { index in // ✅ Limited items
853
+ Text("Item \(index)")
854
+ }
855
+ }
856
+ }
857
+ .animation(.easeInOut(duration: 0.3), value: isExpanded) // ✅ Fast animation
858
+ ```
859
+
860
+ ---
861
+
862
+ ## Quick Performance Checklist
863
+
864
+ ### Critical (Fix Immediately)
865
+ - [ ] No heavy computation in view body
866
+ - [ ] No synchronous I/O on main thread
867
+ - [ ] No blocking operations in view updates
868
+ - [ ] No retain cycles with closures
869
+
870
+ ### High Priority
871
+ - [ ] ForEach uses stable IDs (Identifiable)
872
+ - [ ] Equatable conformance for view models
873
+ - [ ] AsyncImage for remote images
874
+ - [ ] LazyVStack/LazyHStack for large lists
875
+ - [ ] Minimal GeometryReader usage
876
+
877
+ ### Medium Priority
878
+ - [ ] Computed properties for derived data
879
+ - [ ] Pagination for large datasets
880
+ - [ ] Proper task cancellation
881
+ - [ ] Efficient image sizing
882
+ - [ ] Resource cleanup in deinit
883
+
884
+ ### Low Priority
885
+ - [ ] Animation performance optimization
886
+ - [ ] Layout protocol for custom layouts
887
+ - [ ] Image caching strategies
888
+ - [ ] TaskGroup for concurrent operations
889
+
890
+ ---
891
+
892
+ ## Performance Profiling Tips
893
+
894
+ When code review identifies potential performance issues, recommend using Instruments:
895
+
896
+ **Key Instruments:**
897
+ - **Time Profiler**: Identify CPU bottlenecks
898
+ - **Allocations**: Track memory usage
899
+ - **Leaks**: Find retain cycles
900
+ - **SwiftUI**: View body execution tracking
901
+ - **Animation Hitches**: Find janky animations
902
+
903
+ **Common Issues to Profile:**
904
+ - View body execution frequency
905
+ - Layout calculation time
906
+ - Image loading and decoding
907
+ - List scrolling performance
908
+ - Memory growth over time
909
+
910
+ ---
911
+
912
+ ## Version
913
+ **Last Updated**: 2026-02-10
914
+ **Version**: 1.0.0