iOS Code Review Checklist
Rapid-scan checklist for iOS code reviews. Use this to quickly identify issues, then dive into linked documentation for details.
Security Red Flags - Block PR Immediately
- UserDefaults for secrets: Tokens, API keys in UserDefaults → Use Keychain
- Hardcoded secrets: API keys, passwords in source code → Use build configuration or keychain
- No certificate pinning: Production API without certificate pinning → Certificate Pinning
- Sensitive data in logs: Tokens, account numbers, PINs in console logs → Never log sensitive data
- Weak biometric context: Not checking biometric availability before authentication
- HTTP instead of HTTPS: Cleartext traffic without App Transport Security exceptions
- Keychain without accessibility attributes: Missing
kSecAttrAccessibleWhenUnlockedThisDeviceOnly - Biometric data stored: Never store actual biometric data (use LocalAuthentication framework)
- Root/jailbreak detection bypassed: No integrity checks for financial transactions
- Screenshots enabled on sensitive views: No screenshot prevention on payment/auth screens
Common Mistakes
Architecture Violations
- Business logic in Views or ViewControllers → Use ViewModels
- UIKit/SwiftUI dependencies in domain layer → Domain must be framework-independent
- Repository implementations in domain layer → Protocols in domain, implementations in data
- Direct API/Core Data calls from ViewModel (skip use cases) → Use Cases orchestrate
- DTOs exposed to UI layer instead of domain models → Mapper pattern
- Massive ViewModels (>200 lines) → Break into smaller, focused ViewModels
Memory Management
- Strong reference cycles in closures → Use
[weak self]or[unowned self] - Delegates not marked
weak(retain cycles) - Timer not invalidated causing leaks
- NotificationCenter observers not removed
@Publishedproperties capturingselfstrongly- Combine subscriptions not stored (immediate deallocation)
- Large images not released (memory warnings ignored)
SwiftUI Issues
- State mutations not on main thread → Use
@MainActororDispatchQueue.main @Stateused for non-view-local state → Use@StateObjector@ObservedObject- Creating ViewModels inline (
= ViewModel()) instead of dependency injection - Side effects in view body (API calls during render)
- Not using
onAppear/onDisappearfor lifecycle - Missing
.taskmodifier for async operations - Unnecessary view updates (not using
equatableorid)
Combine Issues
- Subscriptions not stored in
cancellables(leaks) - Not using
receive(on: DispatchQueue.main)for UI updates - Infinite loops with circular publishers
- Missing error handling in publisher chains
- Using
PassthroughSubjectfor state → UseCurrentValueSubjector@Published - Not cancelling subscriptions properly
Swift Issues
- Force unwrapping (
!) without justification → Use optional binding - Implicitly unwrapped optionals (
String!) in non-IB contexts - Not using
guardfor early returns - Mutable variables (
var) whenletwould work - Not handling
Resulttype properly (ignoring errors) - Synchronous blocking on main thread
try!without error handling justification
Watch For - Performance & Correctness
Async/Await & Concurrency
- Calling async functions without
await - Not using
Taskfor async operations in SwiftUI - Mixing completion handlers with async/await unnecessarily
- Missing
@MainActoron ViewModels → Main thread safety - Actor isolation violations
- Data races in concurrent code
- Not using
AsyncSequencefor streams
SwiftUI Performance
- Heavy computation in view body (not in
@Stateor.task) - Creating new view instances unnecessarily
- Large lists not using
LazyVStack/LazyHStack - Missing
idinForEachwith complex data - Not using
@ViewBuilderfor conditional views - Excessive view hierarchy depth
- Not leveraging
@Bindingfor two-way state
Core Data Issues
- Blocking fetch requests on main thread → Use
perform/performAndWait - NSManagedObjectContext not thread-safe (used across threads)
- Missing Core Data migrations
- Fetching without
NSFetchRequestbatch size limit - Not using
NSFetchedResultsControllerfor table views - Entities with optional primitives (should be non-optional)
URLSession Issues
- Not handling errors properly (ignoring failure responses)
- Synchronous URLSession calls (blocks thread)
- Not validating HTTP status codes
- Missing timeout intervals
- Certificate pinning not implemented → Certificate pinning
- Not cancelling requests on view dismissal (leaks)
Best Practices - Code Quality
Clean Architecture
- Domain layer is pure Swift (no UIKit/SwiftUI) → Clean Architecture
- Repository protocols in domain, implementations in data → Repository pattern
- Use cases encapsulate business logic → Use Cases
- Coordinator pattern for navigation → Coordinator
- Dependency injection via constructor/protocol → DI Container
Modern iOS
- SwiftUI for UI (not UIKit for new development) → SwiftUI guidelines
- Combine or async/await for async operations → Combine
- URLSession with async/await → Networking
- Core Data for local persistence → Core Data
- Swift concurrency (async/await, actors) → Concurrency
@MainActorfor ViewModels
State Management
@Publishedproperties in ViewModels → MVVM@StateObjectfor ViewModel lifecycle management@ObservedObjectfor passed ViewModels@Bindingfor two-way data flow- Combine publishers for reactive streams
- Result type for error handling
Security
- Keychain for tokens and credentials → Keychain
- LocalAuthentication for biometrics → Biometric Auth
- Certificate pinning for production → Certificate Pinning
- Data Protection API for files → Data Protection
- No sensitive data in logs
Quick Scan Table
| Category | What to Check | Severity | Reference |
|---|---|---|---|
| Security | No UserDefaults for secrets | Block | Keychain |
| Security | No hardcoded API keys | Block | Security |
| Security | Certificate pinning implemented | Block | Certificate Pinning |
| Security | No sensitive data in logs | Block | Security |
| Security | Keychain accessibility set properly | Block | Keychain |
| Architecture | No UIKit/SwiftUI in domain | Block | Clean Architecture |
| Architecture | Use cases encapsulate logic | Fix | Use Cases |
| Architecture | Repository pattern implemented | Fix | Repositories |
| Architecture | No business logic in Views | Fix | MVVM |
| Memory | No retain cycles in closures | Fix | Use [weak self] |
| Memory | Delegates marked weak | Fix | Memory management |
| SwiftUI | State updates on main thread | Fix | Use @MainActor |
| SwiftUI | ViewModels via DI | Fix | DI |
| SwiftUI | Side effects in proper lifecycle | Fix | Use .task/.onAppear |
| Combine | Subscriptions stored | Fix | Store in cancellables |
| Combine | UI updates on main thread | Fix | receive(on: .main) |
| GOOD: | Swift | No force unwrapping (!) | Review |
| GOOD: | Swift | Prefer let over var | Review |
| Async | @MainActor on ViewModels | Fix | Thread safety |
| Core Data | Queries not on main thread | Fix | Use perform |
| URLSession | Requests cancelled properly | Fix | Cancel on dismiss |
Legend:
- Block PR - Security vulnerability or architecture violation
- Fix Before Merge - Correctness issue, performance problem, or likely bug
- [GOOD] Review & Discuss - Code quality improvement
Additional Considerations
Xcode Project Configuration
- Deployment target appropriate (iOS 16+ recommended)
- Swift version consistent across targets
- Build configuration optimized (Debug/Release)
- Code signing configured properly
- Info.plist privacy descriptions provided
- App Transport Security configured
- Bitcode disabled (deprecated)
Testing
- Unit tests for ViewModels → Testing
- Unit tests for Use Cases with mock repositories
- SwiftUI view tests with ViewInspector
- XCTest for domain logic
- UI tests for critical flows (XCUITest)
- Snapshot tests for UI consistency
- Combine publishers tested
Accessibility
- VoiceOver labels on all interactive elements
- Dynamic Type support
- Proper semantic properties in SwiftUI
- Color contrast ratios meet WCAG AA
- Touch targets minimum 44x44 points
- Accessibility identifiers for UI tests
Localization
- All strings in
Localizable.strings - Plural strings with
.stringsdict - Date/time formatted with locale
- Currency formatted appropriately
- RTL layout support
- Region-specific formatting
Block Checklist - Must Fix Before Merge
- No UserDefaults for secrets (use Keychain)
- No hardcoded API keys or credentials
- Certificate pinning for production APIs
- No sensitive data in console logs
- Keychain accessibility properly configured
- No UIKit/SwiftUI dependencies in domain layer
- Use cases encapsulate business logic
- Repository pattern properly implemented
- No strong reference cycles in closures (
[weak self]) - Delegates marked
weak - SwiftUI state updates on main thread
- Combine subscriptions stored in cancellables
- No force unwrapping without justification
- Async operations use async/await or Combine properly
iOS-Specific Testing Checklist
Before approving iOS PRs:
- Unit tests for ViewModels → iOS Testing
- Unit tests for Use Cases with mock repositories
- SwiftUI view tests (ViewInspector or snapshot tests)
- Core Data tests (in-memory store)
- Combine publisher tests
- Edge cases tested (nil, empty, error states)
- Async/await tests with XCTest
- No flaky tests (tests pass consistently)
- Test coverage meets minimum threshold (80%+)
- UI tests for critical user paths (XCUITest)
Related Documentation
iOS Framework Guides:
- iOS Overview - Project setup, dependency injection
- iOS Architecture - Clean Architecture, MVVM, Coordinator
- iOS UI - SwiftUI, navigation, design systems
- iOS Data - URLSession, Core Data, repositories
- iOS Security - Keychain, biometrics, certificate pinning
- iOS Testing - XCTest, UI testing, TDD
- iOS Performance - Instruments, optimization
Swift Language:
- Swift Best Practices - Swift idioms
- Swift Concurrency - async/await, actors
Cross-Platform:
Process: