| name | ios-code-review |
| description | Concise iOS code review for Payoo Merchant app. Focuses on critical/high/medium issues - RxSwift memory leaks, retain cycles, naming conventions, Clean Architecture violations, and business logic placement. Use when reviewing Swift files, pull requests, ViewModels, ViewControllers, UseCases, and Repositories. |
| allowed-tools | Read, Grep, Glob |
iOS Code Review - Priority Issues Focus
Expert iOS code reviewer for Payoo Merchant application, focusing on CRITICAL, HIGH, and MEDIUM priority issues that impact app stability, maintainability, and architecture.
When to Activate
- "review code", "check this file", "review PR"
- Mentions Swift files: ViewController, ViewModel, UseCase, Repository
- "code quality", "best practices", "check standards"
- RxSwift memory leaks, retain cycles, Clean Architecture, MVVM patterns
Review Process
Step 1: Identify Scope
Determine what to review:
- Specific files (e.g., "PaymentViewModel.swift")
- Directories (e.g., "Payment module")
- Git changes (recent commits, PR diff)
- Entire module or feature
Step 2: Read and Analyze
Use Read tool to examine files, focusing on CRITICAL, HIGH, and MEDIUM priority issues only.
Step 3: Apply Priority Standards
🎯 PRIORITY FOCUS AREAS
1. RxSwift Memory Leaks 🔴 CRITICAL
Impact: Memory leaks, app crashes, performance degradation
Check for:
- Missing disposal: Every
.subscribe()MUST have.disposed(by: disposeBag) - Retain cycles: Use
[weak self]in all closures capturingself - DisposeBag: Must be declared as instance variable, not local
- Observable chains: No abandoned subscriptions
Common violations:
// ❌ CRITICAL - Memory leak
observable
.subscribe(onNext: { value in
self.updateUI(value) // Missing disposed(by:)
})
// ❌ CRITICAL - Retain cycle
observable
.subscribe(onNext: { [self] value in // Strong self!
self.updateUI(value)
})
.disposed(by: disposeBag)
// ✅ GOOD
observable
.subscribe(onNext: { [weak self] value in
self?.updateUI(value)
})
.disposed(by: disposeBag)
2. Naming Conventions 🟠 HIGH
Impact: Code readability, maintainability, team collaboration
Check for:
- Types: PascalCase, descriptive (e.g.,
PaymentViewModel, notPmtVM) - Variables: camelCase (e.g.,
paymentAmount, notpmt_amt) - Booleans: Must have
is/has/should/canprefix (e.g.,isLoading, notloading) - NO abbreviations except URL, ID, VC (ViewController), UC (UseCase)
- IBOutlets: Must include type suffix (e.g.,
amountTextField, notamount)
Common violations:
// ❌ BAD
var usr: User?
var loading = false
@IBOutlet weak var amount: UITextField!
var pmtVM: PaymentViewModel?
// ✅ GOOD
var user: User?
var isLoading = false
@IBOutlet weak var amountTextField: UITextField!
var paymentViewModel: PaymentViewModel?
3. Clean Architecture Violations 🟠 HIGH
Impact: Testability, maintainability, architecture integrity
Check for:
- ViewModels: Must extend
BaseViewModel<State>, NO business logic - ViewModel → UseCase: ViewModels MUST call UseCases, NEVER call Repository/API directly
- Business logic: Must be in UseCases ONLY, not in ViewModel/ViewController
- Dependency injection: All dependencies via constructor (Swinject)
- Layer separation: ViewModel → UseCase → Repository → DataSource
Common violations:
// ❌ BAD - ViewModel calling Repository directly
class PaymentViewModel {
private let repository: PaymentRepository
func loadPayments() {
repository.getPayments() // ❌ Skip UseCase layer
.subscribe(onNext: { payments in
// ...
})
.disposed(by: disposeBag)
}
}
// ❌ BAD - Business logic in ViewModel
class PaymentViewModel {
func processPayment(amount: Double) {
if amount <= 0 { return } // ❌ Business logic!
let fee = amount * 0.02
let total = amount + fee
// ...
}
}
// ✅ GOOD - Proper Clean Architecture
class PaymentViewModel: BaseViewModel<PaymentState> {
private let getPaymentsUseCase: GetPaymentsUseCase
private let processPaymentUseCase: ProcessPaymentUseCase
init(getPaymentsUseCase: GetPaymentsUseCase,
processPaymentUseCase: ProcessPaymentUseCase) {
self.getPaymentsUseCase = getPaymentsUseCase
self.processPaymentUseCase = processPaymentUseCase
super.init()
}
func loadPayments() {
getPaymentsUseCase.execute() // ✅ Call UseCase
.subscribe(onNext: { [weak self] payments in
self?.state.accept(.success(payments))
})
.disposed(by: disposeBag)
}
}
4. RxSwift Scheduler Issues 🟡 MEDIUM
Impact: UI freezes, background thread crashes
Check for:
- Background work: Use
.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) - UI updates: Must use
.observeOn(MainScheduler.instance)before UI work - Never block main thread: Network/DB operations on background scheduler
Common violations:
// ❌ BAD - Heavy work on main thread
apiService.getData()
.subscribe(onNext: { data in
// Heavy processing on main thread
self.processLargeData(data)
})
.disposed(by: disposeBag)
// ✅ GOOD - Proper scheduler usage
apiService.getData()
.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background))
.map { data in
// Heavy processing on background
return self.processLargeData(data)
}
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [weak self] result in
// UI updates on main
self?.updateUI(result)
})
.disposed(by: disposeBag)
5. Error Handling 🟡 MEDIUM
Impact: App crashes, poor UX
Check for:
- Observable chains: Must handle
.onErroror usecatchError - API calls: All network operations must have error handling
- User feedback: Show error messages to user
Common violations:
// ❌ BAD - No error handling
apiService.getData()
.subscribe(onNext: { data in
self.updateUI(data)
})
.disposed(by: disposeBag)
// ✅ GOOD - Proper error handling
apiService.getData()
.subscribe(
onNext: { [weak self] data in
self?.updateUI(data)
},
onError: { [weak self] error in
self?.showError(error.localizedDescription)
}
)
.disposed(by: disposeBag)
// ✅ BETTER - Using catchError
apiService.getData()
.catchError { error in
return Observable.just(defaultData)
}
.subscribe(onNext: { [weak self] data in
self?.updateUI(data)
})
.disposed(by: disposeBag)
6. Deprecated Patterns 🟡 MEDIUM
Impact: Future compatibility, best practices
Check for:
- Use BehaviorRelay/PublishRelay instead of BehaviorSubject/PublishSubject
- Avoid Variable: Use BehaviorRelay instead
Common violations:
// ❌ BAD - Using deprecated BehaviorSubject
private let loadingSubject = BehaviorSubject<Bool>(value: false)
// ✅ GOOD - Use BehaviorRelay
private let loadingRelay = BehaviorRelay<Bool>(value: false)
Step 4: Generate Concise Report
Focus ONLY on CRITICAL (🔴), HIGH (🟠), and MEDIUM (🟡) priority issues. Skip low priority findings.
Provide concise, actionable output with:
- Summary: Only 🔴/🟠/🟡 counts (one line per severity)
- Issues: Group by severity, concise title + file + line number
- Code snippets: Only for Critical/High issues, keep minimal
- Quick fixes: Brief, actionable recommendations
Severity Levels - CRITICAL/HIGH/MEDIUM ONLY
🔴 CRITICAL - Fix immediately (blocks release)
- Missing disposal:
.subscribe()without.disposed(by: disposeBag)→ Memory leak - Retain cycles: Strong
selfin closures → Memory leak - UI on background thread: UI updates not on MainScheduler → Crash risk
🟠 HIGH PRIORITY - Fix before merge
- Naming violations: Abbreviations, wrong case, missing is/has prefix, IBOutlet without type suffix
- Architecture violations: ViewModel calling Repository/API directly (skipping UseCase)
- Business logic misplacement: Business logic in ViewModel/ViewController instead of UseCase
- Missing BaseViewModel: ViewModel not extending
BaseViewModel<State>
🟡 MEDIUM PRIORITY - Fix in current sprint
- No error handling: Observable chains without onError or catchError
- Wrong schedulers: Heavy work on main thread, missing observeOn(MainScheduler)
- Deprecated patterns: Using BehaviorSubject/PublishSubject instead of Relay
🚫 IGNORE (Out of Scope)
- Code style and formatting (handled by SwiftLint)
- Documentation and comments
- Accessibility (unless critical)
- Security issues (separate review)
- Performance optimizations (unless critical)
- UI/UX improvements
- Low priority issues
Output Format
KEEP IT CONCISE - Focus on actionable findings only.
# iOS Code Review - Priority Issues
## Summary
🔴 Critical: X | 🟠 High: X | 🟡 Medium: X
## 🔴 CRITICAL ISSUES (Fix Immediately)
1. **Memory leak - Missing disposal** - `PaymentViewModel.swift:45`
```swift
// ❌ observable.subscribe(onNext: { ... }) // No disposal
// ✅ .disposed(by: disposeBag)
- Retain cycle - Strong self -
TransactionViewController.swift:78- Use
[weak self]instead of[self]
- Use
🟠 HIGH PRIORITY (Fix Before Merge)
Naming - Abbreviations -
UserViewModel.swift:12usr→user,pmtVM→paymentViewModel
Architecture - ViewModel calls Repository directly -
PaymentViewModel.swift:34- Inject and call
GetPaymentsUseCaseinstead ofPaymentRepository
- Inject and call
Business logic in ViewModel -
PaymentViewModel.swift:56-60- Move fee calculation to
ProcessPaymentUseCase
- Move fee calculation to
🟡 MEDIUM PRIORITY (Fix This Sprint)
No error handling -
DashboardViewModel.swift:89- Add
onErrororcatchErrorto API call
- Add
Wrong scheduler -
TransactionListViewModel.swift:123- Add
.observeOn(MainScheduler.instance)before UI update
- Add
Deprecated BehaviorSubject -
SettingsViewModel.swift:23- Use
BehaviorRelayinstead
- Use
⚠️ Action Required
- 🔴 X Critical - Block release, fix now
- 🟠 X High - Block merge, fix today
- 🟡 X Medium - Fix in current sprint
✅ Well Done: [If applicable, briefly acknowledge 1-2 good patterns]
## Quick Reference
**Focus on 6 Priority Areas**:
1. 🔴 **RxSwift Memory Leaks** - Missing disposal, retain cycles
2. 🟠 **Naming Conventions** - Abbreviations, wrong case, missing prefixes
3. 🟠 **Clean Architecture** - ViewModel → UseCase → Repository flow
4. 🟡 **Schedulers** - Background work, main thread UI updates
5. 🟡 **Error Handling** - onError, catchError in Observable chains
6. 🟡 **Deprecated Patterns** - BehaviorRelay vs BehaviorSubject
**Skip**: Code style, docs, accessibility, security, performance (unless critical), UI/UX, low priority
## Tips
- **Be concise**: One-line issue descriptions when possible
- **Be specific**: Exact file paths and line numbers
- **Be actionable**: Clear fix instructions
- **Show code**: Only for Critical/High issues, keep minimal
- **Group issues**: Batch similar violations (e.g., "5 naming violations in PaymentViewModel.swift:12,34,56,78,90")
- **No explanations**: Skip "Why" unless unclear - developers know why memory leaks are bad