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

Use 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 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, 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. 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, 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:

{
  "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 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:

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 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:

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, app-logs.txt, device-info.json, summary.json, and summary.md.

Inline Mode for CI/CD Pipelines

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

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:

{
  "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:

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 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 (orchestration), common/idb_utils.py (accessibility), and 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →