# How to Use app_state_capture.py to Diagnose Memory Leaks or Hangs in iOS Simulator Apps

> Diagnose iOS app memory leaks or hangs with app_state_capture.py. Capture logs, screenshots, and hierarchies to detect pressure warnings and UI freezes.

- Repository: [Conor/ios-simulator-skill](https://github.com/conorluddy/ios-simulator-skill)
- Tags: how-to-guide
- Published: 2026-02-27

---

**Use [`app_state_capture.py`](https://github.com/conorluddy/ios-simulator-skill/blob/main/app_state_capture.py) from the `conorluddy/ios-simulator-skill` repository to capture screenshots, accessibility hierarchies, and filtered system logs in a single JSON payload, enabling automated detection of memory pressure warnings and UI freeze states.**

The [`app_state_capture.py`](https://github.com/conorluddy/ios-simulator-skill/blob/main/app_state_capture.py) script is a token-efficient debugging utility designed specifically to help developers diagnose memory leaks and hangs in iOS simulator apps. By orchestrating multiple data sources into a coherent snapshot, it eliminates the need for manual log hunting and provides concrete metrics like `element_count` and `warnings` count that serve as early warning indicators for performance issues.

## Understanding the Diagnostic Architecture

The script centers around the `AppStateCapture` class in [`app_state_capture.py`](https://github.com/conorluddy/ios-simulator-skill/blob/main/app_state_capture.py), which coordinates four primary data collection mechanisms. Each mechanism targets specific diagnostic signals for memory and responsiveness issues.

### Core Data Collection Components

- **`capture_logs`** (lines 82-136): Executes `simctl spawn … log show` filtered by the target app's process name, capturing the last minute of activity and counting `warning` and `error` occurrences. This surfaces iOS memory-pressure events like `Jetsam` and `memory warning`.
- **`capture_accessibility_tree`** (lines 67-80): Retrieves the full UI hierarchy via `get_accessibility_tree` from [`common/idb_utils.py`](https://github.com/conorluddy/ios-simulator-skill/blob/main/common/idb_utils.py). The resulting `element_count` acts as a health indicator—runaway counts (tens of thousands) often indicate view creation leaks.
- **`capture_screenshot`**: Called via the `common` package utilities, captures the current UI state as PNG or base64, providing visual confirmation of frozen spinners or incomplete navigation states.
- **`capture_device_info`** (lines 37-66): Records the simulator's name, UDID, and state from [`common/device_utils.py`](https://github.com/conorluddy/ios-simulator-skill/blob/main/common/device_utils.py), helping correlate memory limits with specific device models (e.g., iPhone 12 vs. iPad).

The `_create_summary_md` method (lines 68-84) collates these into both a markdown report and a machine-readable JSON file, making the output suitable for both human inspection and automated CI pipelines.

## Diagnosing Memory Leaks

Memory leaks in iOS apps typically manifest as excessive resident set size growth, triggering system warnings before eventual termination. The script detects these through two primary vectors:

### Analyzing System Logs for Memory Pressure

The log capture filters for the strings "warning" and "error" in the device's unified logs. When iOS detects excessive memory usage, it emits `memory warning` messages and `Jetsam` event logs (the system's out-of-memory killer). The JSON summary exposes these via `logs.warnings` and `logs.errors` fields.

A non-zero warning count in the output indicates the app has received memory pressure notifications, signaling potential leaks or excessive allocations:

```json
{
  "logs": {
    "captured": true,
    "lines": 200,
    "warnings": 3,
    "errors": 0
  }
}

```

### Detecting Runaway UI Elements

View creation leaks—common in table views or collection views with improper cell reuse—inflate the accessibility tree. The `capture_accessibility_tree` function returns an `element_count` that represents the total number of UI elements currently in the hierarchy.

An `element_count` exceeding 10,000 elements often correlates with memory leaks in view creation, as each element represents allocated backing stores and render layers. Monitor this metric alongside the screenshot to confirm whether the UI complexity matches the visual state.

## Diagnosing Hangs

Hangs and deadlocks require correlating temporal data (log timestamps) with visual state (screenshots) and structural state (accessibility trees).

### Identifying UI Freezes via Screenshots

The `capture_screenshot` utility in [`common/screenshot_utils.py`](https://github.com/conorluddy/ios-simulator-skill/blob/main/common/screenshot_utils.py) returns either a file path or an inline base64 payload depending on the `--inline` flag. When diagnosing hangs, examine the screenshot for static loading spinners, partially rendered navigation bars, or unresponsive button states that persist across multiple captures.

Use the `--size full` preset for detailed visual inspection, or `--size thumb` for rapid CI checks where the base64 string can be embedded directly in logs:

```bash
python scripts/app_state_capture.py \
    --app-bundle-id com.example.myapp \
    --inline \
    --size thumb \
    --log-lines 50

```

### Analyzing Accessibility Tree State

A hung UI often leaves the accessibility tree in an intermediate state—such as a "Loading..." element that never transitions to "Content Loaded." The [`accessibility-tree.json`](https://github.com/conorluddy/ios-simulator-skill/blob/main/accessibility-tree.json) output reveals the exact hierarchy at the moment of capture, helping identify which view controller or thread is blocking the main run loop.

## Practical Usage Examples

### Basic File Mode Capture

For manual debugging sessions, use file mode to generate a timestamped directory containing all diagnostic artifacts:

```bash
python scripts/app_state_capture.py \
    --app-bundle-id com.mycompany.myapp \
    --output ./debug-captures \
    --log-lines 200 \
    --size half

```

This creates `./debug-captures/app-state-20231128-143210/` containing `screenshot.png`, [`accessibility-tree.json`](https://github.com/conorluddy/ios-simulator-skill/blob/main/accessibility-tree.json), [`app-logs.txt`](https://github.com/conorluddy/ios-simulator-skill/blob/main/app-logs.txt), [`device-info.json`](https://github.com/conorluddy/ios-simulator-skill/blob/main/device-info.json), [`summary.json`](https://github.com/conorluddy/ios-simulator-skill/blob/main/summary.json), and [`summary.md`](https://github.com/conorluddy/ios-simulator-skill/blob/main/summary.md).

### Inline Mode for CI/CD Pipelines

For automated regression testing, use inline mode to pipe JSON directly into analysis scripts:

```bash
python scripts/app_state_capture.py \
    --app-bundle-id com.mycompany.myapp \
    --inline \
    --size thumb \
    --log-lines 50

```

The stdout contains a JSON payload suitable for programmatic inspection:

```json
{
  "timestamp": "2023-11-28T14:32:10.123456",
  "screenshot_mode": "inline",
  "screenshot": {
    "mode": "inline",
    "base64": "iVBORw0KGgoAAAANSUhEUgAA...",
    "width": 320,
    "height": 568,
    "size_preset": "thumb"
  },
  "accessibility": {
    "captured": true,
    "element_count": 842
  },
  "logs": {
    "captured": true,
    "lines": 50,
    "warnings": 2,
    "errors": 0
  }
}

```

### Automated Memory Warning Detection

Integrate the following Python script into your CI pipeline to fail builds when memory pressure warnings are detected:

```python
import json
import subprocess
import sys

# Run capture in inline mode

result = subprocess.run(
    [
        "python", "scripts/app_state_capture.py",
        "--app-bundle-id", "com.mycompany.myapp",
        "--inline",
        "--size", "half"
    ],
    capture_output=True,
    text=True,
    check=True
)

summary = json.loads(result.stdout)

# Fail if any memory warnings detected

if summary["logs"]["warnings"] > 0:
    print("⚠️  Potential memory pressure detected")
    print(f"Element count: {summary['accessibility']['element_count']}")
    sys.exit(1)
else:
    print("✅ No memory warnings")

```

## Summary

- **[`app_state_capture.py`](https://github.com/conorluddy/ios-simulator-skill/blob/main/app_state_capture.py)** orchestrates screenshot, accessibility tree, and log capture into a unified diagnostic snapshot via the `AppStateCapture` class.
- **Memory leak indicators** appear in `logs.warnings` (containing `memory warning` or `Jetsam` messages) and `accessibility.element_count` (values over 10,000 suggest view leaks).
- **Hang diagnosis** combines visual inspection of the base64 screenshot with analysis of the accessibility tree's structural state to identify blocked UI transitions.
- **CI integration** is supported through `--inline` mode, which outputs machine-readable JSON for automated failure thresholds.
- **Source files** implementing these features include [`app_state_capture.py`](https://github.com/conorluddy/ios-simulator-skill/blob/main/app_state_capture.py) (orchestration), [`common/idb_utils.py`](https://github.com/conorluddy/ios-simulator-skill/blob/main/common/idb_utils.py) (accessibility), and [`common/screenshot_utils.py`](https://github.com/conorluddy/ios-simulator-skill/blob/main/common/screenshot_utils.py) (visual capture).

## Frequently Asked Questions

### How does app_state_capture.py detect memory leaks specifically?

The script detects memory leaks by analyzing filtered system logs for iOS memory-pressure warnings and by measuring the accessibility tree's `element_count`. High warning counts in `logs.warnings` indicate the system has issued memory warnings to the app, while an abnormally large `element_count` (typically over 10,000) suggests runaway view creation leaking memory through UI element allocation.

### What is the difference between file mode and inline mode?

File mode (default) writes artifacts to a timestamped directory on disk, including separate files for the screenshot, logs, and JSON summary—ideal for manual debugging. Inline mode (`--inline`) outputs a single JSON object to stdout with the screenshot embedded as base64, designed for CI/CD pipelines where you need to parse results programmatically without managing temporary files.

### Can this script detect the exact line of code causing a hang?

No, [`app_state_capture.py`](https://github.com/conorluddy/ios-simulator-skill/blob/main/app_state_capture.py) identifies the *presence* and *visual state* of a hang through screenshots and accessibility tree analysis, but it does not provide stack traces or line-level profiling. For code-level hang detection, pair this script with Xcode's Time Profiler or `sample` command, using the screenshot timestamp to correlate with profiler data.

### How much log history does the script capture by default?

The script captures the last minute of device logs filtered to the target app's process name (controlled via `simctl spawn … log show --last 1m`). You can adjust the line count with `--log-lines`, but the temporal window remains fixed at one minute to maintain token efficiency and capture recent activity relevant to the current app state.