Payoo iOS Code Review
Comprehensive code review for the Payoo Merchant iOS app following Clean Architecture with RxSwift and Swinject.
When to Activate
- "review code", "check code", "code review"
- "review PR", "review pull request", "check pull request"
- "review this file", "check this ViewModel"
- "is this code correct", "any issues with this code"
- When analyzing Swift files in PayooMerchant, Domain, Data, or Analytics layers
Review Process
Step 1: Identify Scope
- Single file review → Read the file
- Multiple files → Use Glob to find related files
- Pull request → Check git diff for changed files
- Full feature → Grep for related ViewModels/UseCases
Step 2: Layer-Specific Checks
For Presentation Layer (PayooMerchant/)
-
MVVM Pattern
- ✓ ViewModel implements
ViewModelTypeprotocol - ✓ Has
InputandOutputnested types - ✓ Has
transform(input:) -> Outputmethod - ✓ ViewControllers bind to Input/Output only
- ✓ No business logic in ViewControllers
- ✓ ViewModel implements
-
RxSwift Memory Management
- ✓ Every ViewController/ViewModel has
DisposeBag - ✓ All subscriptions use
.disposed(by: disposeBag) - ✓ Closures capturing self use
[weak self]or[unowned self] - ✓ No retain cycles in Observable chains
- ✓ Every ViewController/ViewModel has
-
Navigation & DI
- ✓ Navigator passed as dependency (never created directly)
- ✓ UseCases injected via constructor
- ✓ No direct ViewController instantiation
- ✓ Uses factory methods from
ViewControllerFactory
-
Session Error Handling
- ✓ CRITICAL: All API calls have
.catchSessionError(sessionUC) - ✗ Missing
.catchSessionError()→ Session timeout won't logout
- ✓ CRITICAL: All API calls have
For Domain Layer (Domain/)
-
Clean Architecture Rules
- ✓ Pure Swift only (no UIKit imports)
- ✓ No imports from Data or Presentation layers
- ✓ Only protocols for services (no implementations)
- ✓ Models are simple structs/classes
-
UseCase Pattern
- ✓ Protocol defines interface (
UseCaseType) - ✓ Implementation injected with dependencies
- ✓ Single responsibility per UseCase
- ✓ Returns RxSwift Observables/Singles/Maybes
- ✓ Uses
.catchSessionError(sessionUC)for API calls
- ✓ Protocol defines interface (
-
Service Protocols
- ✓ Defined in
Domain/Service/ - ✓ Implemented in Data layer
- ✓ Injected via Swinject
- ✓ Defined in
For Data Layer (Data/)
-
Repository Pattern
- ✓ Implements Domain service protocols
- ✓ Uses Moya for network calls
- ✓ Uses Realm for local storage
- ✓ Converters transform DTOs ↔ Domain models
-
API Models
- ✓ DTOs in
Data/Model/ - ✓ Conform to
DomainConvertibleorRealmRepresentable - ✓ Use ObjectMapper for JSON parsing
- ✓ Don't leak to Domain/Presentation layers
- ✓ DTOs in
Step 3: Project-Wide Checks
-
SwiftLint Compliance
- Run:
./Pods/SwiftLint/swiftlint lint --reporter xcode - Check: Type body length (300/400), file length (800/1200)
- Check: Opt-in rules (empty_count, yoda_condition, todo, etc.)
- Run:
-
Common Pitfalls
- Missing
.catchSessionError()on API observables - Manual ViewController instantiation (should use factory)
- Missing DependencyContainer registration
- Breaking layer boundaries (e.g., Data imported in Domain)
- Missing
disposed(by: disposeBag) - Strong self in closures causing retain cycles
- Using
.count > 0instead of.isEmpty(SwiftLint) - Force unwraps without justification
- Magic numbers without constants
- Missing
-
RxSwift Best Practices
- Use
Driverfor UI bindings (never fails, main thread) - Use
Singlefor one-time operations (network calls) - Use
Observablefor streams - Use
Maybefor optional single values - Prefer
.bind(to:)over.subscribe(onNext:)
- Use
-
Naming Conventions
- ViewModels:
[Feature]ViewModel(e.g.,LoginViewModel,TransactionHistoryViewModel) - ViewControllers:
[Feature]ViewController(e.g.,LoginViewController) - UseCases:
[Action]UseCase(e.g.,GetProfileUseCase,LoginUseCase) - UseCase protocols:
[Action]UseCaseType(e.g.,GetProfileUseCaseType) - Navigators:
[Feature]Navigator(e.g.,LoginNavigator,HomeNavigator) - Navigator protocols:
[Feature]NavigatorType - Services (protocols):
[Name]Service(e.g.,ApiService,LocalStorageService) - Services (impl):
Default[Name]Serviceor[Tech][Name]Service(e.g.,DefaultApiService,RealmStorageService) - Protocols:
[Name]Typesuffix for main protocols - Variables: camelCase, descriptive (avoid abbreviations like
usrNm, useusername) - Constants: camelCase for local, or
kprefix for global (e.g.,kMaxRetryCount) - IBOutlets: Descriptive names with type suffix (e.g.,
loginButton,usernameTextField) - Avoid single letters except in loops (i, j) or common conventions (x, y)
- ViewModels:
Step 4: Generate Report
Format:
## Code Review: [File/Feature Name]
### 📋 Summary
Files: X | 🔴 Critical: X | 🟡 Warning: X | 🔵 Info: X | Status: [✅ Approved / ⚠️ Needs fixes / ❌ Blocked]
### ✅ Strengths
- [List good patterns found]
### ⚠️ Issues Found
#### 🔴 Critical (Must Fix)
**[Issue]** at [file:line]
- **Problem**: [Description]
- **Impact**: [Why critical]
- **Fix**:
\`\`\`swift
// Corrected code
\`\`\`
#### 🟡 Warning (Should Fix)
**[Issue]** at [file:line]
- **Problem**: [Description]
- **Suggestion**: [How to fix]
#### 🔵 Info (Consider)
**[Issue]** at [file:line]
- **Note**: [Observation]
- **Suggestion**: [Optional improvement]
Review Categories
Critical Issues (Must Fix)
- Missing
.catchSessionError()on API calls - Retain cycles / memory leaks
- Breaking Clean Architecture layer boundaries
- Missing DisposeBag disposal
- Force unwraps in unsafe contexts
Warnings (Should Fix)
- Manual ViewController instantiation
- Missing DependencyContainer registration
- SwiftLint violations
- Non-descriptive variable names
- Large type bodies (>300 lines)
Info (Consider)
- Potential optimizations
- Code duplication
- Missing unit tests
- Outdated comments
- TODO/FIXME comments
Quick Commands
Run SwiftLint:
./Pods/SwiftLint/swiftlint lint --reporter xcode
Find files without DisposeBag:
grep -L "DisposeBag" PayooMerchant/**/*ViewModel.swift
Find API calls without catchSessionError:
grep -r "apiService\." --include="*.swift" | grep -v "catchSessionError"
Example Review Flow
- User: "Review LoginViewModel"
- Read
PayooMerchant/Controllers/Login/LoginViewModel.swift - Check MVVM pattern, RxSwift, DI
- Grep for related files (LoginViewController, LoginUseCase)
- Run SwiftLint on the file
- Generate detailed report with line numbers
- Provide fix recommendations
Key Architectural Rules
-
Layer Dependencies
Presentation → Domain ← Data- Presentation can import Domain
- Data can import Domain
- Domain imports nothing (pure Swift)
- NEVER: Domain imports Data/Presentation
-
RxSwift Pattern
// ViewModel transform pattern func transform(input: Input) -> Output { let result = input.trigger .flatMapLatest { [weak self] _ -> Observable<Data> in guard let self = self else { return .empty() } return self.useCase.execute() .catchSessionError(self.sessionUC) // CRITICAL! } return Output(result: result.asDriver(onErrorJustReturn: .empty)) } -
Memory Management
// CORRECT .subscribe(onNext: { [weak self] value in self?.updateUI(value) }).disposed(by: disposeBag) // WRONG - Retain cycle! .subscribe(onNext: { value in self.updateUI(value) }).disposed(by: disposeBag)
Output Format
Always provide:
- Clear issue categorization (Critical/Warning/Info)
- File paths with line numbers for clickable links
- Code snippets showing the problem
- Concrete fix recommendations
- Summary with metrics
Reference: See standards.md for detailed coding standards and examples.md for review examples.
