Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Solo iOS Growth Diary — Vol. 2: Speed Is The Fe...

Solo iOS Growth Diary — Vol. 2: Speed Is The Feature (Instrumenting iOS Cold Start with MetricKit)

Cold start is the quietest retention lever an iOS app has. We cut ours from 1,240ms to 310ms in seven moves — no rewrites, nine evenings, zero new dependencies — and day-30 retention almost doubled.

Vol. 2 of the Captio Revival Notes series for solo iOS founders. Inside: how to measure cold start with MetricKit (the histogram bins Apple gives you, and the per-launch timer that fills the gap), the seven specific moves that delivered 930ms of savings (drop the default scene, lazy-load fonts, JSON→Plist, strip ViewInspector & Lottie, pre-warm Keychain, defer first network call ≥500ms, move Core Data setup off main thread), three counter-arguments and their honest answers, and a 5-rule speed budget any indie iOS dev can adopt this week.

Includes Swift code snippets you can paste tonight (MXMetricManagerSubscriber + ProcessInfo.systemUptime time-to-first-frame), a per-device-class p50 dashboard, and the retention curve that proved every millisecond paid back.

New volume every Tuesday. Vol. 1 (From EOL to Encore) sets the EOL Heir thesis. Vol. 3 (next week): why SMTP outlives iCloud — email-as-sync architecture in 2026.

Follow: speakerdeck.com/simplememo · x.com/simplememofast

Avatar for SimpleMemo

SimpleMemo

May 08, 2026

More Decks by SimpleMemo

Other Decks in Programming

Transcript

  1. V O L . 0 2 S O L O

    iO S G R O W T H D IA R Y Speed Is The Feature. Instrumenting iOS cold start with MetricKit — and the seven moves that cut our boot time 73%. 1, 240 m s → 310 m s · m easur ed on a r eal devi ce, not a w i shl i st By SimpleMemo · @simplememofast speaker deck. com / si m pl em em o · Capt i o Revi val Not es Seri es 01 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  2. R E C A P — V O L .

    1 Last week: the EOL Heir thesis. Vol. 1 argued that beloved-but-dead apps are frozen acquisition pools — and that picking three pillars (0.3 s launch, email-as-sync, zero deps) beats cloning a feature list. This week is a deep zoom on Pillar I — the boring, compounding lever that ships retention quietly: launch speed. 02 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  3. T H I S D E C K Today's roadmap.

    I Why speed compounds The retention math nobody puts in a pitch. II What MetricKit hides Apple gives you bins, not values. Read between them. III The seven moves Each step removed one lazy assumption. IV Counter-arguments ‘Users don't notice’ — and why that's still half-wrong. V Instrumentation playbook Code you can paste tonight. VI Vol. 3 teaser Email-as-Sync: why SMTP outlives iCloud. 03 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  4. Nobody writes you a review that says ‘fast’. They write

    you reviews only when they keep coming back. 04 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  5. I — W H Y S P E E D

    C O M P O U N D S Cold start is a per-session tax. Every interaction with a memo app starts with a launch. A user who captures three ideas a day pays the cold-start tax three times. Save 600 ms once and that user gets 1.8 seconds of life back per day, every day, until they delete the app. 3 captures/day × 600 ms saved × 365 days = 657 seconds/year. That's ~11 minutes of friction removed per active user per year — invisible to them, audible to retention. 05 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  6. T H E C H A I N Why speed

    turns into retention. 1 Latency low Capture latency below 500 ms keeps thought intact. 2 Habit forms Friction-free reps build a daily ritual. 3 Active days rise More active days = more captures per cohort. 4 Reviews appear Habitual users leave the kind of reviews that move ASO. 5 Refugee inflow Reviews boost rank → inflow accelerates → loop closes. 06 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  7. I I — W H A T M E T

    R I C K I T S H O W S Y O U MetricKit gives you bins, not values. MetricKit's `MXAppLaunchMetric` reports cold start as bucketed histograms (`MXHistogram`) — not raw timestamps. You learn the distribution shape, but not the tail you actually care about. For solo devs that means: combine MetricKit (free, privacy-safe, AppStore-compliant) with a tiny on-device timer recorded from `applicationDidFinishLaunching` to first frame. Together they cover both the macro distribution and the micro causes. 07 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  8. S U B S C R I B E Listen

    for MetricKit payloads in 12 lines. i m port M et ri cKi t f i nal cl ass M et ri csO bserver : NSO bj ect , M XM et ri cM anagerSubscri ber { f unc di dRecei ve( _ payl oads: [ M XM et ri cPayl oad] ) { f or p i n payl oads { guar d l et l aunch = p. appl i cat i onLaunchM et ri cs el se { cont i nue } / / hi st ogram of col d- l aunch durat i ons ( seconds) l et h = l aunch. hi st ogram m edTi m eToFi rst D raw l et p50 = h. per cent i l e( 0. 5) , p90 = h. per cent i l e( 0. 9) st or e( "col d_p50", p50) ; st or e( "col d_p90", p90) } } } 08 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  9. R E F E R E N C E T

    A R G E T S What ‘fast enough’ actually is. Threshold Cold start User experience Instant < 300 ms Feels native; no perceptible boot. Brisk 300–500 ms Acceptable for most categories. Noticeable 500–800 ms Memo apps lose the 5-second-thought race. Slow 800–1200 ms Habit erosion compounds. Bad > 1200 ms Default Xcode template territory. 09 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  10. I I I — T H E S E V

    E N M O V E S 1,240 ms → 310 ms in seven moves. Each move was small. Each one removed an assumption Xcode shipped me by default. Total elapsed time: nine evenings, no rewrites. 1 Drop the default scene 260 m s saved 2 Lazy-load font registration 120 m s 3 Replace JSON config with Data+Plist 90 m s 4 Strip ViewInspector & Lottie 210 m s 5 Pre-warm Keychain on app boot 70 m s 6 Defer first network call ≥500 ms 140 m s 7 Move Core Data setup off main thread 40 m s Σ Cumulative 930 m s 10 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  11. M O V E 0 1 Drop the default scene.

    Xcode's SwiftUI App template instantiates `WindowGroup { ContentView() }`. That's 260 ms of scene plumbing on first launch — multiple `UIScene` lifecycle calls, environment graph setup, accessibility audit, and view-hierarchy diff. For a single-screen memo app, that's overhead you don't need. Replace `App` with a UIKit `UIApplicationDelegate` + a single `UIHostingController(rootView:)`. No `WindowGroup`. The hosting controller still hosts SwiftUI — you keep declarative views, lose the scene tax. 11 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  12. M O V E 0 2 Lazy-load font registration. Most

    indie apps register custom fonts in `Info.plist` via `UIAppFonts`. CoreText loads and validates every entry at launch — even fonts only used on settings screens. Move them out of `UIAppFonts`. Register on demand with `CTFontManagerRegisterFontsForURL` the first time the relevant screen appears. Tradeoff: a 60 ms hitch the first time settings is opened. Worth it for 120 ms of compound first-launch savings. 12 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  13. M O V E 0 3 Replace JSON config with

    Data + Plist. Decoding even a 4 KB JSON config file at launch costs 70–110 ms — JSONDecoder warms up reflection, builds a key path tree, allocates and discards intermediate strings. PropertyListDecoder costs 18 ms for the same payload. If your config is fixed at compile time, ship it as a `.plist` resource. If it's truly dynamic, decode it after first frame, not before. 13 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  14. M O V E 0 4 Strip ViewInspector & Lottie.

    Two of the most common indie iOS dependencies in 2024–2025 each cost ~100 ms of dyld load and class registration at app launch — even if you never call them in the launch path. The cost is the linker cost. ViewInspector Used only in tests. Move to a test-target dependency, not main target. Lottie Replace with `CAEmitterLayer` or pre-rendered video for splash/celebration screens. Lottie's runtime is 1.2 MB and 110 ms. 14 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  15. M O V E 0 5 Pre-warm Keychain on app

    boot. First Keychain access in a session costs 70–140 ms — `securityd` IPC is lazy. Subsequent calls are <2 ms. If you're going to read a token at first interaction anyway, read it before first frame so the user never waits. Surprisingly, you can do this in `application(_:didFinishLaunchingWithOptions:)` without measurably hurting cold start — Keychain bring-up parallelizes with view loading. 15 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  16. M O V E 0 6 Defer the first network

    call ≥ 500 ms. URLSession warm-up is dominated by TLS handshake (~120 ms on Wi-Fi, ~280 ms on LTE) and DNS resolution. Both compete with main-thread layout if you fire them at launch. Use `DispatchQueue.main.asyncAfter(deadline: .now() + 0.5)` for the first config fetch. The user is reading the cursor anyway. Network calls before first frame are almost always wasted optimism. 16 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  17. M O V E 0 7 Move Core Data setup

    off the main thread. `NSPersistentContainer.loadPersistentStores` is synchronous-by-default and blocks the main run loop. On a fresh install it costs 30–80 ms. On migration runs it can spike to 300+ ms — and Apple's diagnostic tools won't flag it. Use `loadPersistentStores(completionHandler:)` and assert the container is ready in your view's first action, not at app launch. 17 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  18. C U M U L A T I V E

    Each move, ranked by impact. After Move 1 After Move 2 After Move 3 After Move 4 After Move 5 After Move 6 After Move 7 0 200 400 600 800 1000 1200 980 860 770 560 490 350 310 Y-axis: cold-start time remaining (ms). Lower is better. 18 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  19. I V — C O U N T E R

    - A R G U M E N T S ‘Users don't notice 1 second.’ Half-true. Users won't write a review saying ‘it's fast’. But they will quietly stop opening an app whose response time slips above the comfortable threshold. The metric you should care about isn't ‘did they comment?’ — it's ‘did they come back?’ Counter-evidence: in our v0.3 → v0.5 cohort, day-7 capture frequency rose 1.7× after launch dropped from 720 ms to 310 ms. The cohort never mentioned speed in a review. They mentioned ‘the app I keep using.’ 19 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  20. C O U N T E R - A R

    G U M E N T I I ‘The bottleneck is the network, not launch.’ True for some categories (chat, stocks). False for memo apps. Capture is local-first — no network round-trip blocks the cursor. Users open the app to write, not to fetch. Removing 600 ms from cold start is removing 600 ms from the path-to-first-character. If your category is network-bound, this Vol. 2 doesn't apply. Vol. 6 (ASO with no budget) might be more your stop. 20 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  21. C O U N T E R - A R

    G U M E N T I I I ‘Device variance ruins the metric.’ Device variance is real — iPhone 15 Pro launches in 220 ms, iPhone 12 mini in 310 ms, iPhone XR in 480 ms (same binary). The instinct is to give up and not measure. Wrong move. Bucket by device class (`UIDevice.modelIdentifier`) and track median per bucket. Variance is your friend — it tells you which devices need extra love. Our XR users got the biggest retention lift after Move 4. 21 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  22. H O N E S T C A V E

    A T When NOT to optimise launch. Pre-PMF If you don't have ten weekly active users, fix retention by talking to users, not profiling. Niche tools Apps users open once a week (tax filing, travel) get less compound benefit. Heavy first-frame If your first frame requires a 5 MB asset (image editing), buying back 600 ms is fighting physics. 22 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  23. V — P L A Y B O O K

    What to measure, in order. 1 Time to first frame From process start to first `UIView/UIViewController.viewDidAppear` — your single most important number. 2 Time to interactive From first frame to first usable input (cursor visible, keyboard up). 3 Time to capture saved End-to-end: launch → write → tap Send → confirmation. 4 MetricKit p50/p90 Aggregate distribution from the App Store cohort. 5 Per-device-class p50 iPhone 12, 13, 14, 15 — track separately, optimize the slowest 25%. 23 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  24. S N I P P E T Measure time-to-first-frame. /

    / I n AppD el egat e. sw i f t l et pr ocessSt art = Pr ocessI nf o. pr ocessI nf o. syst em Upt i m e / / I n your r oot vi ew ' s f i rst appearance: ext ensi on Root Host i ngCont r ol l er { over ri de f unc vi ew D i dAppear( _ ani m at ed: Bool ) { super . vi ew D i dAppear( ani m at ed) l et el apsed = Pr ocessI nf o. pr ocessI nf o. syst em Upt i m e - pr ocessSt art M et ri cs. r ecor d( . t i m eToFi rst Fram e, m s: I nt ( el apsed * 1000) ) } } 24 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  25. O U T P U T What the dashboard looks

    like. W1 W2 W3 W4 W5 W6 W7 W8 0 200 400 600 800 1000 1200 1400 1600 iPhone 12 mini (p50) iPhone 13 (p50) iPhone XR (p50) Slowest device class converged with newer hardware after Moves 4–6 — same code, very different perceived experience. 25 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  26. B L I N D S P O T S

    Three things MetricKit will never tell you. Per-build attribution MetricKit aggregates across builds within a 24h window. To attribute a regression to a specific commit, tag your own metric with `Bundle.main.infoDictionary["CFBundleVersion"]`. First launch vs warm MetricKit folds first-install cold launches into the same histogram as subsequent cold launches. Track `isFirstLaunch` yourself; it's a different beast. Migration spikes Core Data migrations and StoreKit-2 transaction restoration each add 100–500 ms. MetricKit hides these in tail bins. Annotate them explicitly. 26 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  27. T O O L S What we actually use. Need

    Tool Cost Note Aggregate distribution MetricKit (MXMetricManager) Free Privacy-safe, App Store-compliant Per-launch precise time ProcessInfo.systemUptime Free On-device, send to your own backend Trace flame graph Xcode Instruments — App Launch Free Local only; one device at a time dyld trace DYLD_PRINT_STATISTICS=1 Free See dynamic-linker time Crash + hang attribution MetricKit hangDiagnostics Free Find launch hangs in the wild 27 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  28. O U T C O M E What the retention

    curve actually did. D1 D2 D3 D5 D7 D14 D30 0 10 20 30 40 50 60 70 80 90 100 Cohort with 720 ms launch (v0.3) Cohort with 310 ms launch (v0.5) Y-axis: % of D0 cohort still active. Day-30 retention almost doubled. Same UI, same brand, only launch latency changed. 28 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  29. H I D D E N C O S T

    Every dependency you add is a launch tax. Apple's ‘1 second to first frame’ guideline isn't a UX nicety — it's the budget you spend on every new SDK. CocoaPods/SPM line items are not free. They're 60–200 ms each, even with deferred init. Before you add a dependency, ask: ‘Is this 100 ms of cold start worth more than what I'm getting?’ If you can't answer yes confidently, you can't add it. 29 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  30. D E C I S I O N R U

    L E Speed budget per release. 1 Set a hard ceiling p50 cold-start ≤ 400 ms on the slowest device-class your app supports. 2 Reject regressions Any commit that pushes p50 above the ceiling is reverted, not optimised later. 3 Track the trend Every release notes the p50 in the changelog. Future-you needs the data. 4 Earn the budget If you absolutely need a heavy dependency, pay for it by shaving 1.5× elsewhere. 5 Talk about it publicly Public commitments to a number force you to keep the number. (This deck is one such commitment.) 30 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  31. H O N E S T R E F L

    E C T I O N Three mistakes I made profiling. Profiled on the wrong device iPhone 15 Pro hides nearly every problem you'd care about. Always start on the slowest device in your support matrix. Trusted Instruments alone Instruments measures a single launch perfectly — useless for the long-tail distribution. MetricKit is the macro lens you need. Ignored first-install paths First-launch is a different code path with TLS warmup, Keychain bring-up, and disk reads that subsequent launches skip. Measure both. 31 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  32. T H E H O N E S T S

    C O R E B O A R D Vol. 2 receipts. 73% boot reduction 1,240ms → 310ms +1.7× D7 captures v0.3 → v0.5 cohorts +86% D30 retention 22% → 41% 9 evenings no rewrites, no team 0 deps added every move was a removal 32 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  33. I F Y O U ' R E A S

    O L O I O S D E V Copy. Skip. Steal. C O P Y • Set a numeric ceiling • Profile on the slowest device • Strip ViewInspector & Lottie • Time-to-first-frame metric S K IP • Profiling pre-PMF • Optimising network-bound apps via launch • Heavy first-frame UX (image editors) S T E A L • Per-device-class p50 dashboard • Public ceiling commitment • Dependency = ms cost framing 33 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  34. Speed is the feature you're allowed to charge for without

    anyone noticing. It compounds. It survives features being added. It's the hardest moat to copy because nobody talks about it out loud. 34 / 35 Sol o i O S G r ow t h D i ary · Vol . 2
  35. N E X T T U E S D A

    Y — V O L . 0 3 ‘Email-as-Sync’ — why SMTP outlives iCloud. How email became our spine, what an email-based outbox looks like in Swift, and why we'd bet on it for the next decade — even though everyone declares email dead every other year. Follow this series speaker deck. com / si m pl em em o New volume every Tuesday for 12 weeks. Talk to the builder x. com / si m pl em em of ast Replies open. Questions sharpen the next deck. 35 / 35 Sol o i O S G r ow t h D i ary · Vol . 2