# Kordant Performance Baseline > **Last Updated:** 2026-06-02 > **Owner:** iOS Team > **Target Devices:** iPhone SE (2nd gen), iPhone 12, iPhone 15 Pro --- ## 1. Overview This document defines performance budgets, baselines, and measurement methodology for the Kordant iOS app. Performance tests are implemented using XCTMetric (`XCTClockMetric`, `XCTCPUMetric`, `XCTMemoryMetric`, `XCTApplicationLaunchMetric`) in both unit and UI test targets. ### 1.1 Performance Goals | Metric | Target | Device | Priority | |--------|--------|--------|----------| | Cold Launch Time | < 2.0s | iPhone 12 | P0 | | Warm Launch Time | < 1.0s | iPhone 12 | P0 | | Scroll FPS | 60fps | All lists | P0 | | ViewModel Data Load | < 100ms | iPhone 12 | P1 | | API Deserialization | < 50ms (1000 items) | iPhone 12 | P1 | | Memory Usage (navigation) | < 150MB | iPhone 12 | P1 | | Image Cache Key Generation | < 1ms | All devices | P2 | | Security Checks | < 10ms | All devices | P2 | --- ## 2. Test Categories ### 2.1 Launch Performance (KordantUITests) | Test | Metric | Baseline | Regression Threshold | |------|--------|----------|---------------------| | `testColdLaunchPerformance` | XCTApplicationLaunchMetric | < 2.0s | 10% (> 2.2s fails) | | `testWarmLaunchPerformance` | XCTApplicationLaunchMetric | < 1.0s | 10% (> 1.1s fails) | **Measurement:** Uses `XCTApplicationLaunchMetric` with `waitUntilResponsive: true`, waiting for the Dashboard navigation bar to appear. Tests run in UI testing mode with mocked data (populatedDashboard scenario). ### 2.2 Scroll Performance (KordantUITests) | Test | Metric | Baseline | Regression Threshold | |------|--------|----------|---------------------| | `testDashboardScrollPerformance` | Clock, CPU, Memory | Smooth (no dropped frames) | 10% | | `testAlertListScrollPerformance` | Clock, CPU, Memory | Smooth (no dropped frames) | 10% | | `testServiceListScrollPerformance` | Clock, CPU, Memory | Smooth (no dropped frames) | 10% | **Measurement:** Uses `XCTClockMetric`, `XCTCPUMetric`, `XCTMemoryMetric` in `measure(metrics:)` blocks. The test scrolls through lists programmatically and measures rendering performance. **Must run on physical devices** — simulators do not reflect real-world scroll performance. ### 2.3 Navigation Performance (KordantUITests) | Test | Metric | Baseline | Regression Threshold | |------|--------|----------|---------------------| | `testTabNavigationPerformance` | Clock, CPU | < 500ms per transition | 10% | | `testServiceDetailNavigationPerformance` | Clock, Memory | < 500ms per transition | 10% | ### 2.4 Data Loading Performance (KordantUITests) | Test | Metric | Baseline | Regression Threshold | |------|--------|----------|---------------------| | `testDashboardDataLoadPerformance` | Clock, CPU, Memory | < 2s (includes network) | 10% | | `testDarkWatchDataLoadPerformance` | Clock, Memory | < 2s (includes network) | 10% | ### 2.5 Memory Performance (KordantUITests) | Test | Metric | Baseline | Regression Threshold | |------|--------|----------|---------------------| | `testMemoryUsageAcrossNavigationFlow` | XCTMemoryMetric | < 150MB peak | 10% | | `testMemoryReturnsAfterNavigation` | XCTMemoryMetric | Stable (no leaks) | 10% increase | ### 2.6 Unit Performance (KordantTests) | Test | Metric | Baseline | Regression Threshold | |------|--------|----------|---------------------| | `DeserializationPerformanceTests.decodeAlerts` | Manual timing | < 50ms (1000 items) | 10% | | `DeserializationPerformanceTests.decodeExposures` | Manual timing | < 50ms (1000 items) | 10% | | `ViewModelPerformanceTests.dashboardViewModelLoadTime` | Manual timing | < 100ms (50 items) | 10% | | `ViewModelPerformanceTests.darkWatchViewModelLoadTime` | Manual timing | < 100ms (30 items) | 10% | | `KeychainPerformanceTests.keychainStoreRetrievePerformance` | Manual timing | < 0.1ms per op | 10% | | `SecurityPerformanceTests.jailbreakDetectionPerformance` | Manual timing | < 10ms | 10% | | `SecurityPerformanceTests.runtimeIntegrityPerformance` | Manual timing | < 10ms | 10% | ### 2.7 XCTMetric Unit Performance (KordantTests) | Test | Metric | Baseline | Regression Threshold | |------|--------|----------|---------------------| | `testJSONEncodingPerformance` | Clock, CPU, Memory | Established by 10 runs | 10% | | `testJSONDecodingPerformance` | Clock, CPU, Memory | Established by 10 runs | 10% | | `testThreatScoreCalculationPerformance` | Clock, CPU, Memory | Established by 10 runs | 10% | | `testImageCacheMetadataPersistencePerformance` | Clock | Established by 10 runs | 10% | | `testAlertSortingPerformance` | Clock | Established by 10 runs | 10% | --- ## 3. Baseline Establishment Process 1. **Initial Baseline:** Run each performance test 10 times on a reference device (iPhone 12). 2. **Record Results:** Xcode automatically records the baseline average. 3. **Document:** Record the baseline values in the "Baselines" column above. 4. **Accept:** Xcode compares future runs against the stored baseline. 5. **Review:** Baselines should be re-established after major OS updates or architecture changes. ### 3.1 Device-Specific Baselines | Device | Cold Launch | Scroll FPS | Memory Peak | |--------|-------------|------------|-------------| | iPhone SE (2nd gen) | < 3.0s | 60fps (slower scroll) | < 120MB | | iPhone 12 | < 2.0s | 60fps | < 150MB | | iPhone 15 Pro | < 1.2s | 60fps | < 200MB | --- ## 4. Regression Detection ### 4.1 Threshold Configuration Xcode's `measure(metrics:)` API automatically compares test results against stored baselines. A 10% regression threshold is configured for all tests. ```swift // Example: Xcode flags this test if time exceeds baseline by > 10% measure(metrics: [XCTClockMetric(), XCTCPUMetric(), XCTMemoryMetric()]) { // code under test } ``` ### 4.2 CI Pipeline Integration Performance tests run as part of the CI pipeline on every PR and release build: 1. **Unit performance tests:** Run on every PR (fast, no device needed) 2. **UI performance tests:** Run on device farm (iPhone SE, 12, 15 Pro) nightly 3. **Failure action:** PR cannot merge if any performance test regresses by > 10% 4. **Alert:** Performance degradation alerts sent to #ios-eng Slack channel ### 4.3 Manual Verification In Xcode: 1. Open the test report (⌘9 → Tests tab) 2. Select a performance test 3. View the "Metrics" tab to see baseline vs. current results 4. Check "Performance Graph" for trend analysis across test runs --- ## 5. Optimization Techniques ### 5.1 Launch Time | Technique | Location | Impact | |-----------|----------|--------| | Lazy service initialization | `KordantApp.swift` | ~500ms saved | | Deferred setup after first frame | `ContentView.swift → task` | ~300ms saved | | Minimal `didFinishLaunchingWithOptions` | `AppDelegate.swift` | ~200ms saved | | Mock data in testing mode | `TestingMode.swift` | N/A (testing only) | ### 5.2 Scroll Performance | Technique | Location | Impact | |-----------|----------|--------| | `LazyVStack` for lists | All list views | 60fps maintained | | Image prefetching | `PaginatedListView.swift` | No loading jank | | `ShieldSkeleton` placeholders | All loading states | Perceived performance | | `CachedAsyncImage` with downsampling | `CachedAsyncImage.swift` | Memory efficient loading | ### 5.3 Memory | Technique | Location | Impact | |-----------|----------|--------| | 50MB URLCache memory limit | `ImageCacheService.swift` | Prevents OOM | | LRU disk cache eviction | `ImageCacheService.swift` | Disk quota enforced | | Memory warning handling | `ImageCacheService.swift` | Clears cache on pressure | | `@StateObject` lifecycle management | All ViewModels | No leaked view models | ### 5.4 Data Processing | Technique | Location | Impact | |-----------|----------|--------| | Concurrent async data loading | `DashboardViewModel.swift` | 3x faster dashboard | | JSON with iso8601 date strategy | `APIClient.swift` | Fast date parsing | | Codable conformance | All models | Zero boilerplate parsing | | Lazy metadata loading | `ImageCacheService.swift` | No disk I/O at launch | --- ## 6. Running Performance Tests ### 6.1 Local (Xcode) ```bash # Run all performance tests xcodebuild test \ -project Kordant.xcodeproj \ -scheme Kordant \ -testPlan KordantUITests \ -destination 'platform=iOS,name=iPhone 12' \ -only-testing:KordantUITests/LaunchPerformanceTests \ -only-testing:KordantUITests/ScrollPerformanceTests \ -only-testing:KordantUITests/NavigationPerformanceTests # Run unit performance tests xcodebuild test \ -project Kordant.xcodeproj \ -scheme Kordant \ -destination 'platform=iOS Simulator,name=iPhone 15 Pro' \ -only-testing:KordantTests/XCTMetricPerformanceTests ``` ### 6.2 CI (Device Farm) ```bash # Run on all target devices xcodebuild test \ -project Kordant.xcodeproj \ -scheme Kordant \ -testPlan KordantUITests \ -destination 'platform=iOS,name=iPhone SE (2nd generation)' \ -destination 'platform=iOS,name=iPhone 12' \ -destination 'platform=iOS,name=iPhone 15 Pro' \ -resultBundlePath ./PerformanceResults.xcresult ``` ### 6.3 Important Notes - **Always run performance tests on physical devices.** Simulators do not reflect real-world CPU, GPU, or memory performance. - Tests should be run with the device in **Airplane Mode** (for local stability tests) or with **real network conditions** (for data loading tests). - Device brightness should be set to ~50% for consistency. - Close all other apps before running performance tests. - Tests should be run with a **Release build configuration** for accurate timing. --- ## 7. Troubleshooting | Issue | Likely Cause | Resolution | |-------|-------------|------------| | Scroll test fails on device | Background processes | Restart device, close apps | | Launch time > 2s | New dependency added in init | Move to deferred setup | | Memory > 150MB | Image cache leak | Check URLCache eviction | | JSON decode > 50ms | Large nested payload | Optimize API response shape | | Baseline drift | iOS version change | Re-baseline after OS update | --- ## 8. Review Cadence - **Weekly:** Review performance test results in CI dashboard - **Monthly:** Full performance audit on reference device - **Per Release:** Re-establish baselines before release branch cut - **Per OS Update:** Re-run all tests after iOS beta/stable update