RORK LABJP
MAX — Rork Max builds native Swift apps instead of React Native for iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessageNATIVE — Rork Max unlocks native features like AR/LiDAR scanning, Metal 3D games, widgets, and Dynamic IslandAPPLE — It also reaches advanced Apple capabilities such as HealthKit, HomeKit, NFC, App Clips, and on-device Core MLRN — Standard Rork builds iOS and Android from a single prompt using React Native (Expo)PRICE — Rork is free to start, with paid plans beginning at $25 per monthGROWTH — Rork now sees 743,000+ monthly visits, 85% growth, and recently raised $15MMAX — Rork Max builds native Swift apps instead of React Native for iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessageNATIVE — Rork Max unlocks native features like AR/LiDAR scanning, Metal 3D games, widgets, and Dynamic IslandAPPLE — It also reaches advanced Apple capabilities such as HealthKit, HomeKit, NFC, App Clips, and on-device Core MLRN — Standard Rork builds iOS and Android from a single prompt using React Native (Expo)PRICE — Rork is free to start, with paid plans beginning at $25 per monthGROWTH — Rork now sees 743,000+ monthly visits, 85% growth, and recently raised $15M
Articles/Dev Tools
Dev Tools/2026-07-10Advanced

The Termination That Never Shows Up as a Crash — Reading JetsamEvent in Rork Apps

Crashlytics is silent, yet reviewers write that the app closes by itself. Most of the time the OS killed it for exceeding its memory limit. Here is how to read JetsamEvent reports and design an image-heavy app's memory budget from measured values.

Rork496Rork Max219Memory4iOS107Indie Development20

Premium Article

For a while, one of my wallpaper apps collected App Store reviews saying the app closed on its own after a minute or two, while the Crashlytics dashboard stayed completely quiet. The only reproduction step anyone offered was "scroll a lot." Nothing happened on my own device, no matter how long I scrolled.

The answer showed up when I opened Settings, then Privacy & Security, then Analytics & Improvements, then Analytics Data on a device, and found a stack of files named JetsamEvent-2026-…. The app had not crashed. iOS had killed it for using too much memory, which is why nothing ever reached the crash reporter.

Apps built with Rork or Rork Max sit in exactly the same position. However tidy the generated code is, the memory ceiling iOS grants a process does not move. If anything, generated code tends to be generous with images and lists, which makes that ceiling easier to touch.

A Jetsam kill is not a crash

iOS runs a mechanism called Jetsam. When system-wide memory gets tight, or when a single process exceeds its per-device limit, the kernel sends that process a SIGKILL and reclaims it.

SIGKILL cannot be handled. That is the crux of the problem. Crashlytics and Sentry install signal and exception handlers to catch failures. A Jetsam kill gives them no opportunity to run, so nothing is recorded — and nothing appears under Crashes in App Store Connect either.

Instead, these terminations land in MetricKit as MXAppExitMetric, specifically cumulativeMemoryResourceLimitExitCount. Plenty of indie developers never look there. I did not either.

Type of terminationReaches the crash reporter?Where to look
Exceptions and signals (SIGSEGV, etc.)YesCrashlytics / Organizer > Crashes
Memory limit exceeded (Jetsam)NoOn-device Analytics Data / MetricKit
Watchdog (slow launch)SometimesOrganizer > Hangs, 0x8badf00d
Reclaimed while backgroundedNoNormal behavior. No action needed

That last row matters more than it looks. I wrote up the staged-release approach itself in a five-tier memory pressure release architecture for iOS. Reclaiming a backgrounded app is ordinary iOS behavior. Trying to eliminate every JetsamEvent will sink you into pointless work. The ones worth chasing are the kills that happened in the foreground.

Reading a JetsamEvent report

Pull the file out of Analytics Data on the device — the share button will hand it to AirDrop or Files. The contents are JSON-flavored text.

Start with the header near the top.

{"bug_type":"298","timestamp":"2026-06-14 21:03:11.42 +0900","os_version":"iPhone OS 26.1 (23B82)",
 "incident_id":"…","pageSize":16384,"memoryStatus":{"compressorSize":98213,"memoryPages":{"active":41022,
 "free":1290,"wired":38310}}}

A bug_type of 298 means Jetsam. Note pageSize: 16384, or 16 KB. Assuming 4096 throws every later conversion off by a factor of four. Devices from the A14 onward use 16 KB pages, so always read this field rather than hardcoding it.

Then find the victim. The body is an array of process states, each shaped like this.

{"uuid":"…","states":["frontmost"],"killDelta":0,"genCount":0,"age":133,"purgeable":0,
 "fds":124,"coalition":312,"rpages":86214,"reason":"per-process-limit",
 "name":"WallpaperApp","cpuTime":41.2,"idleDelta":0}

Three fields carry the signal.

  1. Whether states contains frontmost — if it does, the app vanished while the user was looking at it
  2. reasonper-process-limit means your app blew its own ceiling; vm-pageshortage means it got caught in a system-wide squeeze
  3. rpages — resident pages. Convert with rpages × pageSize ÷ 1024 ÷ 1024

In this example, 86214 × 16384 ÷ 1048576 ≈ 1,347 MB. The app hit the roughly 1,384 MB limit on an iPhone SE (3rd generation) and was killed.

Doing that by hand gets old, so here is a script.

#!/usr/bin/env bash
# jetsam-summary.sh — summarize .ips files pulled from Analytics Data
# usage: ./jetsam-summary.sh ~/Downloads/JetsamEvent-*.ips
for f in "$@"; do
  python3 - "$f" <<'PY'
import json, sys, re
raw = open(sys.argv[1], encoding="utf-8").read()
# the header line and the body are concatenated; split on the first newline
head, body = raw.split("\n", 1) if "\n" in raw else (raw, "{}")
page = json.loads(head).get("pageSize", 4096)
ts   = json.loads(head).get("timestamp", "?")
for m in re.finditer(r'\{"uuid".*?\}', body):
    p = json.loads(m.group(0))
    mb = p.get("rpages", 0) * page / 1048576
    if p.get("reason") in ("per-process-limit", "vm-pageshortage") and mb > 100:
        front = "FRONTMOST" if "frontmost" in p.get("states", []) else "background"
        print(f'{ts}  {p["name"]:<22} {mb:8.1f} MB  {p["reason"]:<18} {front}')
PY
done

I ran it across 41 reports covering three weeks. Twelve were foreground per-process-limit kills, and eleven of those happened on the same screen — the wallpaper grid. From there it stopped being a mystery and became an implementation problem.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
How to convert rpages in a JetsamEvent report into MB and identify which process died at what size
Measured limits from os_proc_available_memory(): 1,384 MB on an iPhone SE versus 2,099 MB on an iPhone 13
Swift and React Native implementations that pin the image cache to 25% of the remaining memory budget
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

Dev Tools2026-06-21
Reclaiming the Pre-Submit Checks You Lose When Publishing Without Xcode
Rork Max's two-click publishing skips the Xcode Organizer, making it easy to miss usage strings, version bumps, and Bundle IDs. Here is a quick pre-submit self-audit you can run yourself, with how to read the generated files.
Dev Tools2026-07-08
Working Around Rork Max's 20-Geofence Wall with Dynamic Re-registration
In a native Swift app generated by Rork Max, geofences you registered quietly stop firing past a certain count — and it's almost always iOS's silent limit of 20 monitored regions per app. Here's a dynamic re-registration design that keeps only the nearest 20 live, plus a Swift implementation you can drop in.
Dev Tools2026-07-06
Migrating Rork Max SwiftUI to @Observable: Narrowing the Re-renders ObservableObject Was Spreading
Rork Max tends to generate SwiftUI apps built on ObservableObject and @Published, where a single state change re-evaluates every subscribing view. Moving to the Observation framework's @Observable narrows invalidation to the property level. Here is the migration path, plus the view-body execution counts I measured in Instruments before and after.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →