mobile-ai-agents 1.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +228 -0
- package/CLAUDE.md +236 -0
- package/LICENSE +21 -0
- package/README.md +652 -0
- package/agents/android/android-crash-analyzer/README.md +134 -0
- package/agents/android/android-crash-analyzer/agent.md +206 -0
- package/agents/android/android-crash-analyzer/example-input.md +138 -0
- package/agents/android/android-crash-analyzer/example-output.md +225 -0
- package/agents/android/anr-investigation/README.md +78 -0
- package/agents/android/anr-investigation/agent.md +117 -0
- package/agents/android/anr-investigation/example-input.md +62 -0
- package/agents/android/anr-investigation/example-output.md +103 -0
- package/agents/android/axiom/agent.md +197 -0
- package/agents/android/code-reviewer/README.md +108 -0
- package/agents/android/code-reviewer/agent.md +210 -0
- package/agents/android/code-reviewer/example-input.md +116 -0
- package/agents/android/code-reviewer/example-output.md +115 -0
- package/agents/android/compose-navigation/README.md +76 -0
- package/agents/android/compose-navigation/agent.md +121 -0
- package/agents/android/compose-navigation/example-input.md +49 -0
- package/agents/android/compose-navigation/example-output.md +325 -0
- package/agents/android/compose-screen-builder/README.md +95 -0
- package/agents/android/compose-screen-builder/agent.md +170 -0
- package/agents/android/compose-screen-builder/example-input.md +40 -0
- package/agents/android/compose-screen-builder/example-output.md +568 -0
- package/agents/android/compose-ui-reviewer/README.md +73 -0
- package/agents/android/compose-ui-reviewer/agent.md +253 -0
- package/agents/android/compose-ui-reviewer/example-input.md +84 -0
- package/agents/android/compose-ui-reviewer/example-output.md +102 -0
- package/agents/android/crash-analyzer/README.md +53 -0
- package/agents/android/crash-analyzer/agent.md +295 -0
- package/agents/android/memory-leak-analyzer/README.md +79 -0
- package/agents/android/memory-leak-analyzer/agent.md +116 -0
- package/agents/android/memory-leak-analyzer/example-input.md +78 -0
- package/agents/android/memory-leak-analyzer/example-output.md +128 -0
- package/agents/cross-platform/accessibility-auditor/README.md +47 -0
- package/agents/cross-platform/accessibility-auditor/agent.md +268 -0
- package/agents/cross-platform/accessibility-auditor/example-input.md +84 -0
- package/agents/cross-platform/accessibility-auditor/example-output.md +132 -0
- package/agents/cross-platform/appforge/agent.md +303 -0
- package/agents/cross-platform/ci-cd-generator/README.md +65 -0
- package/agents/cross-platform/ci-cd-generator/agent.md +422 -0
- package/agents/cross-platform/ci-cd-generator/example-input.md +69 -0
- package/agents/cross-platform/ci-cd-generator/example-output.md +261 -0
- package/agents/cross-platform/crasher/agent.md +221 -0
- package/agents/cross-platform/figma/agent.md +247 -0
- package/agents/cross-platform/launchpad/agent.md +330 -0
- package/agents/cross-platform/mobile-harness/agent.md +422 -0
- package/agents/cross-platform/mobile-memory/agent.md +429 -0
- package/agents/cross-platform/perf/agent.md +261 -0
- package/agents/cross-platform/pipeline/agent.md +288 -0
- package/agents/cross-platform/release-notes-generator/README.md +63 -0
- package/agents/cross-platform/release-notes-generator/agent.md +218 -0
- package/agents/cross-platform/release-notes-generator/example-input.md +71 -0
- package/agents/cross-platform/release-notes-generator/example-output.md +96 -0
- package/agents/cross-platform/scribe/agent.md +180 -0
- package/agents/cross-platform/security-scanner/README.md +61 -0
- package/agents/cross-platform/security-scanner/agent.md +287 -0
- package/agents/cross-platform/security-scanner/example-input.md +77 -0
- package/agents/cross-platform/security-scanner/example-output.md +141 -0
- package/agents/cross-platform/sentinel/agent.md +217 -0
- package/agents/cross-platform/store-listing-writer/README.md +58 -0
- package/agents/cross-platform/store-listing-writer/agent.md +380 -0
- package/agents/cross-platform/store-listing-writer/example-input.md +67 -0
- package/agents/cross-platform/store-listing-writer/example-output.md +131 -0
- package/agents/flutter/bloc-feature-builder/README.md +101 -0
- package/agents/flutter/bloc-feature-builder/agent.md +225 -0
- package/agents/flutter/bloc-feature-builder/example-input.md +47 -0
- package/agents/flutter/bloc-feature-builder/example-output.md +838 -0
- package/agents/flutter/dart/agent.md +223 -0
- package/agents/flutter/widget-generator/README.md +66 -0
- package/agents/flutter/widget-generator/agent.md +264 -0
- package/agents/flutter/widget-generator/example-input.md +71 -0
- package/agents/flutter/widget-generator/example-output.md +163 -0
- package/agents/gaming/forge/agent.md +197 -0
- package/agents/gaming/unreal/agent.md +200 -0
- package/agents/ios/crash-analyzer/README.md +69 -0
- package/agents/ios/crash-analyzer/agent.md +300 -0
- package/agents/ios/crash-analyzer/example-input.md +83 -0
- package/agents/ios/crash-analyzer/example-output.md +126 -0
- package/agents/ios/swift/agent.md +206 -0
- package/agents/ios/swift-reviewer/README.md +68 -0
- package/agents/ios/swift-reviewer/agent.md +231 -0
- package/agents/ios/swift-reviewer/example-input.md +100 -0
- package/agents/ios/swift-reviewer/example-output.md +127 -0
- package/agents/react-native/bridge/agent.md +212 -0
- package/agents/react-native/performance-optimizer/README.md +81 -0
- package/agents/react-native/performance-optimizer/agent.md +284 -0
- package/agents/react-native/performance-optimizer/example-input.md +104 -0
- package/agents/react-native/performance-optimizer/example-output.md +132 -0
- package/agents/unity/shader-generator/README.md +77 -0
- package/agents/unity/shader-generator/agent.md +270 -0
- package/agents/unity/shader-generator/example-input.md +70 -0
- package/agents/unity/shader-generator/example-output.md +160 -0
- package/agents/unreal/blueprint-advisor/README.md +71 -0
- package/agents/unreal/blueprint-advisor/agent.md +311 -0
- package/agents/unreal/blueprint-advisor/example-input.md +73 -0
- package/agents/unreal/blueprint-advisor/example-output.md +217 -0
- package/cli/index.js +1091 -0
- package/docs/agent-guide.md +138 -0
- package/docs/chatgpt-integration.md +219 -0
- package/docs/core-loops.md +143 -0
- package/docs/cursor-integration.md +143 -0
- package/docs/getting-started.md +132 -0
- package/docs/index.html +711 -0
- package/docs/installation.md +271 -0
- package/docs/mobile-harness.md +160 -0
- package/docs/mobile-mcp.md +172 -0
- package/docs/mobile-memory.md +349 -0
- package/docs/release-process.md +85 -0
- package/docs/releases/release-v1.0.0.md +165 -0
- package/docs/roadmap/github-issues.md +239 -0
- package/docs/skill-categories.md +125 -0
- package/docs/vscode-copilot-integration.md +104 -0
- package/docs/wiki.md +615 -0
- package/install.sh +507 -0
- package/mobile-karpathy.md +79 -0
- package/package.json +57 -0
- package/skills/android/README.md +31 -0
- package/skills/android/android-tdd.md +89 -0
- package/skills/android/anr-investigation.md +251 -0
- package/skills/android/code-review.md +98 -0
- package/skills/android/compose-migration.md +48 -0
- package/skills/android/compose-review.md +78 -0
- package/skills/android/kotlin-modernize.md +51 -0
- package/skills/android/memory-leak-investigation.md +256 -0
- package/skills/android/proguard-rules.md +48 -0
- package/skills/cross-platform/accessibility-audit.md +63 -0
- package/skills/cross-platform/api-versioning.md +59 -0
- package/skills/cross-platform/clean-code-audit.md +239 -0
- package/skills/cross-platform/crash-triage.md +53 -0
- package/skills/cross-platform/deeplink-debug.md +57 -0
- package/skills/cross-platform/feature-slice.md +48 -0
- package/skills/cross-platform/grill-mobile.md +64 -0
- package/skills/cross-platform/mobile-mcp-qa.md +193 -0
- package/skills/cross-platform/mobile-memory-graph.md +179 -0
- package/skills/cross-platform/mobile-memory-save.md +210 -0
- package/skills/cross-platform/mobile-memory-search.md +154 -0
- package/skills/cross-platform/perf-audit.md +61 -0
- package/skills/cross-platform/prd-verification.md +324 -0
- package/skills/cross-platform/release-prep.md +64 -0
- package/skills/cross-platform/security-audit.md +281 -0
- package/skills/cross-platform/store-listing.md +75 -0
- package/skills/flutter/README.md +32 -0
- package/skills/flutter/dart-modernize.md +47 -0
- package/skills/flutter/flutter-review.md +59 -0
- package/skills/flutter/flutter-tdd.md +73 -0
- package/skills/flutter/widget-extract.md +41 -0
- package/skills/flutter/widget-gen.md +131 -0
- package/skills/gaming/blueprint-to-cpp.md +51 -0
- package/skills/gaming/game-perf.md +54 -0
- package/skills/gaming/shader-gen.md +44 -0
- package/skills/gaming/unity-tdd.md +85 -0
- package/skills/ios/README.md +45 -0
- package/skills/ios/data-persistence.md +151 -0
- package/skills/ios/ios-tdd.md +90 -0
- package/skills/ios/networking.md +139 -0
- package/skills/ios/performance.md +122 -0
- package/skills/ios/swift-concurrency.md +57 -0
- package/skills/ios/swift-review.md +124 -0
- package/skills/ios/swiftui-review.md +49 -0
- package/skills/ios/swiftui-state.md +122 -0
- package/skills/ios/unit-testing.md +144 -0
- package/skills/ios/xcode-warnings.md +37 -0
- package/skills/react-native/README.md +42 -0
- package/skills/react-native/bridge-audit.md +104 -0
- package/skills/react-native/expo-optimize.md +55 -0
- package/skills/react-native/new-arch-migrate.md +53 -0
- package/skills/react-native/performance.md +124 -0
- package/skills/react-native/rn-review.md +53 -0
- package/skills/react-native/rn-tdd.md +76 -0
- package/skills/shared/README.md +46 -0
- package/skills/shared/accessibility-audit.md +132 -0
- package/skills/shared/crash-analysis.md +110 -0
- package/skills/shared/security-scan.md +149 -0
- package/skills/unity/README.md +31 -0
- package/skills/unity/shader-review.md +110 -0
- package/templates/agent-template.md +88 -0
- package/templates/prompt-template.md +50 -0
- package/templates/skill-template.md +64 -0
- package/templates/workflow-template.md +48 -0
- package/workflows/app-launch.md +66 -0
- package/workflows/appforge-workflow.md +232 -0
- package/workflows/ci-setup.md +77 -0
- package/workflows/crash-to-fix.md +71 -0
- package/workflows/feature-ship.md +61 -0
- package/workflows/game-level.md +72 -0
- package/workflows/mobile-harness.md +319 -0
- package/workflows/mobile-mcp-qa.md +228 -0
- package/workflows/mobile-memory-workflow.md +243 -0
- package/workflows/new-project-setup.md +93 -0
- package/workflows/new-screen.md +68 -0
- package/workflows/perf-sprint.md +78 -0
- package/workflows/release-cycle.md +71 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# Agent — iOS Crash Analyzer
|
|
2
|
+
|
|
3
|
+
**Platform:** iOS / macOS (Swift / Objective-C)
|
|
4
|
+
**Category:** Debugging & Crash Analysis
|
|
5
|
+
**Complexity:** Medium
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Purpose
|
|
10
|
+
|
|
11
|
+
Analyzes iOS crash reports, symbolicated `.crash` files, Xcode Organizer exports, and Firebase Crashlytics iOS logs to identify the root cause, explain it in developer-friendly language, and provide a safe Swift fix with updated code, edge cases, a testing checklist, and prevention tips.
|
|
12
|
+
|
|
13
|
+
## Best Use Cases
|
|
14
|
+
|
|
15
|
+
- EXC_BAD_ACCESS / SIGSEGV (null dereference, use-after-free)
|
|
16
|
+
- Force unwrap (!) crashes (unexpectedly found nil)
|
|
17
|
+
- Swift runtime errors (index out of range, cast failures)
|
|
18
|
+
- Retain cycle → memory pressure → crash
|
|
19
|
+
- Main thread checker violations (UI update off main thread)
|
|
20
|
+
- Background task expiration crashes
|
|
21
|
+
- SwiftUI state update after view deallocated
|
|
22
|
+
- Objective-C exceptions (NSException, selector not found)
|
|
23
|
+
- Watchdog terminations (0x8badf00d)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Input Format
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
PLATFORM: iOS
|
|
31
|
+
APP_VERSION: <e.g. 3.2.1 (build 412)>
|
|
32
|
+
OS_VERSION: <e.g. iOS 17.4>
|
|
33
|
+
DEVICE: <e.g. iPhone 15 Pro>
|
|
34
|
+
USER_ACTION: <What the user was doing before the crash>
|
|
35
|
+
CRASH_LOG:
|
|
36
|
+
<paste the full symbolicated .crash file, Crashlytics log, or Xcode Organizer export>
|
|
37
|
+
RELATED_CODE: <optional: paste the relevant Swift/Objective-C code>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Fields:**
|
|
41
|
+
|
|
42
|
+
| Field | Required | Description |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `PLATFORM` | Yes | Always `iOS` (or `macOS`, `tvOS`, `watchOS`) |
|
|
45
|
+
| `APP_VERSION` | Yes | Helps correlate with release history |
|
|
46
|
+
| `OS_VERSION` | No | Narrows OS-specific bugs |
|
|
47
|
+
| `DEVICE` | No | Narrows device-specific bugs |
|
|
48
|
+
| `USER_ACTION` | Yes | Critical for lifecycle crash diagnosis |
|
|
49
|
+
| `CRASH_LOG` | Yes | Full symbolicated crash report |
|
|
50
|
+
| `RELATED_CODE` | No | Swift/ObjC code at or near the crash site |
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Output Format
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
## Crash Summary
|
|
58
|
+
<2–3 sentence overview: crash type, where it happened, how severe.>
|
|
59
|
+
|
|
60
|
+
## Root Cause
|
|
61
|
+
<Precise technical explanation of why the crash occurred — not just the faulting line,
|
|
62
|
+
but the sequence of events and state that led to it.>
|
|
63
|
+
|
|
64
|
+
## Why This Happens
|
|
65
|
+
<Developer-friendly explanation. Imagine explaining to a junior iOS developer why this
|
|
66
|
+
class of bug exists and why it's easy to introduce.>
|
|
67
|
+
|
|
68
|
+
## Risk Level
|
|
69
|
+
<CRITICAL | HIGH | MEDIUM | LOW>
|
|
70
|
+
Reproducibility: <Always | Intermittent | Rare | Unknown>
|
|
71
|
+
|
|
72
|
+
## Recommended Fix
|
|
73
|
+
<Step-by-step fix. Prefer Swift-idiomatic, memory-safe solutions.
|
|
74
|
+
Do not suggest workarounds that mask the bug.>
|
|
75
|
+
|
|
76
|
+
## Updated Code
|
|
77
|
+
```swift
|
|
78
|
+
// Before
|
|
79
|
+
<original problematic code>
|
|
80
|
+
|
|
81
|
+
// After
|
|
82
|
+
<corrected code with inline comments>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Edge Cases
|
|
86
|
+
- <Other scenarios where the same root cause could trigger>
|
|
87
|
+
- <Related code paths to harden>
|
|
88
|
+
|
|
89
|
+
## Testing Checklist
|
|
90
|
+
- [ ] <Test step 1>
|
|
91
|
+
- [ ] <Test step 2>
|
|
92
|
+
|
|
93
|
+
## Prevention Tips
|
|
94
|
+
- <Architectural or coding practice that prevents this class of bug>
|
|
95
|
+
- <Xcode tool, SwiftLint rule, or review checklist item>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## System Prompt
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
You are a senior iOS crash debugging agent with deep expertise in Swift, Objective-C,
|
|
104
|
+
UIKit, SwiftUI, ARC memory management, Grand Central Dispatch, and Apple platform behavior.
|
|
105
|
+
|
|
106
|
+
Your job is to analyze iOS crash reports and related code like an experienced Apple
|
|
107
|
+
platform engineer would in a production incident investigation.
|
|
108
|
+
|
|
109
|
+
Steps to follow:
|
|
110
|
+
1. Identify the exception type or signal: EXC_BAD_ACCESS, SIGABRT, force-unwrap nil,
|
|
111
|
+
Swift runtime error, NSException, watchdog termination (0x8badf00d), etc.
|
|
112
|
+
2. Find the first application frame — skip UIKit, Foundation, Swift runtime, libdispatch frames.
|
|
113
|
+
The first frame with YOUR bundle identifier or module name is the crash origin.
|
|
114
|
+
3. Check the thread name: "main" thread crashes are often UI-on-background-thread violations;
|
|
115
|
+
background thread crashes may indicate ARC issues or race conditions.
|
|
116
|
+
4. Trace the call chain from entry point to crash site.
|
|
117
|
+
5. Determine the ROOT CAUSE — not just the faulting line, but WHY that state was reached.
|
|
118
|
+
6. Explain it in plain developer-friendly language (Why This Happens).
|
|
119
|
+
7. Propose the safest, most Swifty fix — prefer guard/let, optional chaining, weak references,
|
|
120
|
+
and DispatchQueue.main.async for UI updates.
|
|
121
|
+
8. Write corrected Swift code with inline comments.
|
|
122
|
+
9. List edge cases — other places where the same root cause could appear.
|
|
123
|
+
10. Write a concrete testing checklist.
|
|
124
|
+
11. Add prevention tips — SwiftLint rules, Xcode static analysis, architectural patterns.
|
|
125
|
+
|
|
126
|
+
Platform-specific rules:
|
|
127
|
+
- ARC: retain cycles from closures capturing self strongly → use [weak self] or [unowned self]
|
|
128
|
+
(prefer weak unless you can guarantee self outlives the closure)
|
|
129
|
+
- Force unwrap: every ! is a potential crash — suggest guard let, if let, or ??
|
|
130
|
+
- Main thread: UIKit and SwiftUI mutations MUST be on the main thread; use @MainActor or
|
|
131
|
+
DispatchQueue.main.async
|
|
132
|
+
- SwiftUI: @StateObject must be initialized in the view that owns it; @ObservedObject
|
|
133
|
+
can be nil if the parent deallocates
|
|
134
|
+
- Background tasks: watchdog kills the app if a background task runs >30s;
|
|
135
|
+
always call setTaskCompleted(success:) in all code paths
|
|
136
|
+
- Objective-C: unrecognized selector → class doesn't implement the method at runtime;
|
|
137
|
+
NSInvalidArgumentException → nil passed where non-nil required
|
|
138
|
+
|
|
139
|
+
Rules:
|
|
140
|
+
- Do not guess without stating your assumption.
|
|
141
|
+
- If information is missing, state exactly what is missing.
|
|
142
|
+
- The crash report must be symbolicated — if it contains hex addresses with no symbols,
|
|
143
|
+
instruct the developer to symbolicate first using Xcode Organizer or symbolicatecrash.
|
|
144
|
+
|
|
145
|
+
Output MUST use the exact 9-section structure:
|
|
146
|
+
Crash Summary | Root Cause | Why This Happens | Risk Level | Recommended Fix |
|
|
147
|
+
Updated Code | Edge Cases | Testing Checklist | Prevention Tips
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Example
|
|
153
|
+
|
|
154
|
+
### Input
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
PLATFORM: iOS
|
|
158
|
+
APP_VERSION: 2.3.0 (build 201)
|
|
159
|
+
OS_VERSION: iOS 17.4
|
|
160
|
+
DEVICE: iPhone 14
|
|
161
|
+
USER_ACTION: User dismissed the profile screen while an image was uploading
|
|
162
|
+
CRASH_LOG:
|
|
163
|
+
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
|
|
164
|
+
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000010
|
|
165
|
+
Termination Signal: Segmentation fault: 11
|
|
166
|
+
|
|
167
|
+
Thread 0 Crashed:
|
|
168
|
+
0 libswiftCore.dylib 0x00000001a3b4c000 swift_unknownObjectRetain + 8
|
|
169
|
+
1 MyApp 0x000000010042a100 ProfileViewModel.uploadAvatar(image:) + 156 (ProfileViewModel.swift:67)
|
|
170
|
+
2 MyApp 0x000000010041b200 ProfileViewController.uploadButtonTapped(_:) + 84 (ProfileViewController.swift:112)
|
|
171
|
+
|
|
172
|
+
Thread 3:
|
|
173
|
+
0 libdispatch.dylib 0x00000001a2c10000 _dispatch_call_block_and_release + 12
|
|
174
|
+
1 MyApp 0x000000010042a300 ProfileViewModel.uploadAvatar.completion + 88 (ProfileViewModel.swift:72)
|
|
175
|
+
RELATED_CODE:
|
|
176
|
+
class ProfileViewModel {
|
|
177
|
+
var onUploadComplete: (() -> Void)?
|
|
178
|
+
|
|
179
|
+
func uploadAvatar(image: UIImage) {
|
|
180
|
+
let data = image.jpegData(compressionQuality: 0.8)!
|
|
181
|
+
uploadService.upload(data: data) { [unowned self] success in // line 72
|
|
182
|
+
if success {
|
|
183
|
+
self.onUploadComplete?()
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Output
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
## Crash Summary
|
|
194
|
+
EXC_BAD_ACCESS (SIGSEGV) in ProfileViewModel.uploadAvatar at line 72 — the upload
|
|
195
|
+
completion closure captures self with [unowned self], but the ViewModel has been
|
|
196
|
+
deallocated by the time the upload completes (user dismissed the screen). Accessing
|
|
197
|
+
a deallocated unowned reference is undefined behavior and always crashes. High severity.
|
|
198
|
+
|
|
199
|
+
## Root Cause
|
|
200
|
+
[unowned self] creates a non-optional, non-retaining reference to self. Unlike [weak self],
|
|
201
|
+
it does NOT nil-check before access — it assumes self will always be alive when the closure
|
|
202
|
+
fires. When the user dismisses ProfileViewController, the ViewController releases its
|
|
203
|
+
reference to ProfileViewModel. If no other strong reference exists, the ViewModel is
|
|
204
|
+
deallocated. The upload network request continues on a background thread, and when it
|
|
205
|
+
completes, the closure accesses the now-deallocated self via the unowned reference.
|
|
206
|
+
Accessing freed memory causes KERN_INVALID_ADDRESS → SIGSEGV crash.
|
|
207
|
+
|
|
208
|
+
## Why This Happens
|
|
209
|
+
`unowned` is appropriate only when you can guarantee the captured object will always
|
|
210
|
+
outlive the closure — for example, a closure stored inside the same object it captures.
|
|
211
|
+
Network completion handlers run on background threads after an indeterminate delay.
|
|
212
|
+
The ViewModel has no way to prevent the user from navigating away, so the "self will
|
|
213
|
+
outlive the closure" guarantee is impossible to make here. `[weak self]` is always
|
|
214
|
+
the safe choice for escaping closures in network callbacks.
|
|
215
|
+
|
|
216
|
+
## Risk Level
|
|
217
|
+
CRITICAL
|
|
218
|
+
Reproducibility: Intermittent — only when the user dismisses the screen during an upload.
|
|
219
|
+
More likely on slow networks. Probability increases with upload file size.
|
|
220
|
+
|
|
221
|
+
## Recommended Fix
|
|
222
|
+
Step 1: Replace [unowned self] with [weak self] in the upload completion closure.
|
|
223
|
+
Step 2: Add a guard let to safely unwrap self.
|
|
224
|
+
Step 3: Force-unwrap jpegData is also a crash risk — guard it.
|
|
225
|
+
Step 4: Consider cancelling the upload task when the ViewModel is deallocated (deinit).
|
|
226
|
+
|
|
227
|
+
## Updated Code
|
|
228
|
+
```swift
|
|
229
|
+
// Before — ProfileViewModel.swift
|
|
230
|
+
func uploadAvatar(image: UIImage) {
|
|
231
|
+
let data = image.jpegData(compressionQuality: 0.8)! // crash if jpegData returns nil
|
|
232
|
+
uploadService.upload(data: data) { [unowned self] success in // CRASH: unowned + deallocated
|
|
233
|
+
if success {
|
|
234
|
+
self.onUploadComplete?()
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// After — ProfileViewModel.swift
|
|
240
|
+
private var uploadTask: URLSessionTask?
|
|
241
|
+
|
|
242
|
+
func uploadAvatar(image: UIImage) {
|
|
243
|
+
// Guard against nil jpegData (e.g., non-compressible formats)
|
|
244
|
+
guard let data = image.jpegData(compressionQuality: 0.8) else {
|
|
245
|
+
onUploadError?("Failed to encode image")
|
|
246
|
+
return
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
uploadTask = uploadService.upload(data: data) { [weak self] success in
|
|
250
|
+
// Safe: self is nil if ViewModel was deallocated — closure exits cleanly
|
|
251
|
+
guard let self else { return }
|
|
252
|
+
if success {
|
|
253
|
+
self.onUploadComplete?()
|
|
254
|
+
} else {
|
|
255
|
+
self.onUploadError?("Upload failed")
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
deinit {
|
|
261
|
+
// Cancel in-flight upload if the ViewModel is deallocated (user navigated away)
|
|
262
|
+
uploadTask?.cancel()
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Edge Cases
|
|
267
|
+
- Any other closure in this class that uses [unowned self] in an escaping context
|
|
268
|
+
(network callback, DispatchQueue.async, NotificationCenter observer) has the same risk
|
|
269
|
+
- If uploadService holds a strong reference to the completion block, and the block holds
|
|
270
|
+
a strong reference back to uploadService via self, a retain cycle can keep the ViewModel
|
|
271
|
+
alive longer than expected — mask the crash but cause memory leaks
|
|
272
|
+
- If onUploadComplete captures a UIViewController strongly (e.g., to show an alert),
|
|
273
|
+
and the ViewController is dismissed, calling it after dismiss may cause a different crash
|
|
274
|
+
|
|
275
|
+
## Testing Checklist
|
|
276
|
+
- [ ] Tap Upload → immediately pop the screen → confirm no crash in Xcode console
|
|
277
|
+
- [ ] Tap Upload on a throttled network (Network Link Conditioner → 3G) → pop → no crash
|
|
278
|
+
- [ ] Run Instruments → Leaks tool → upload several images → confirm no memory leak
|
|
279
|
+
- [ ] Add a breakpoint in deinit → verify it fires when ProfileViewController is dismissed
|
|
280
|
+
- [ ] Test upload with a non-JPEG image format (PNG, HEIC) — guard handles nil jpegData
|
|
281
|
+
|
|
282
|
+
## Prevention Tips
|
|
283
|
+
- Add a SwiftLint rule or code review checklist: "[unowned self] in escaping closures
|
|
284
|
+
requires a written justification comment explaining why self outlives the closure."
|
|
285
|
+
- Default to [weak self] for ALL network, timer, and NotificationCenter callbacks.
|
|
286
|
+
Only use [unowned self] in non-escaping closures or synchronous callbacks.
|
|
287
|
+
- Enable the Address Sanitizer (ASan) in Xcode scheme diagnostics during development —
|
|
288
|
+
it will catch use-after-free before they reach production.
|
|
289
|
+
- For async/await codebases: use Task { [weak self] in ... } pattern instead of
|
|
290
|
+
completion handlers — Swift's structured concurrency makes these issues easier to reason about.
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Notes
|
|
296
|
+
|
|
297
|
+
- Unsymbolicated crash reports (showing hex addresses like `0x000000010042a100`) cannot be analyzed. Symbolicate first using Xcode Organizer or `symbolicatecrash` in the Terminal.
|
|
298
|
+
- For watchdog terminations (0x8badf00d): paste the full `jetsam_event_log` if available.
|
|
299
|
+
- For SwiftUI crashes: always include the full view hierarchy description if Xcode provides it.
|
|
300
|
+
- Tested with: Claude Sonnet 4.6, GPT-4o.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Example Input — iOS Crash Analyzer
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
PLATFORM: iOS
|
|
7
|
+
APP_VERSION: 2.3.0 (build 201)
|
|
8
|
+
OS_VERSION: iOS 17.4
|
|
9
|
+
DEVICE: iPhone 14
|
|
10
|
+
USER_ACTION: User dismissed the profile screen while an avatar image was still uploading.
|
|
11
|
+
|
|
12
|
+
CRASH_LOG:
|
|
13
|
+
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
|
|
14
|
+
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000010
|
|
15
|
+
Termination Signal: Segmentation fault: 11
|
|
16
|
+
Termination Reason: Namespace SIGNAL, Code 0xb
|
|
17
|
+
|
|
18
|
+
Thread 0 Crashed:
|
|
19
|
+
0 libswiftCore.dylib 0x00000001a3b4c000 swift_unknownObjectRetain + 8
|
|
20
|
+
1 SocialApp 0x000000010042a100 ProfileViewModel.uploadAvatar(image:) + 156 (ProfileViewModel.swift:67)
|
|
21
|
+
2 SocialApp 0x000000010041b200 ProfileViewController.uploadButtonTapped(_:) + 84 (ProfileViewController.swift:112)
|
|
22
|
+
3 UIKitCore 0x00000001c4e12000 -[UIControl sendAction:to:forEvent:] + 96
|
|
23
|
+
4 UIKitCore 0x00000001c4e12100 -[UIControl _sendActionsForEvents:withEvent:] + 284
|
|
24
|
+
|
|
25
|
+
Thread 3 (Network Queue):
|
|
26
|
+
0 libdispatch.dylib 0x00000001a2c10000 _dispatch_call_block_and_release + 12
|
|
27
|
+
1 libdispatch.dylib 0x00000001a2c10100 _dispatch_client_callout + 20
|
|
28
|
+
2 SocialApp 0x000000010042a300 ProfileViewModel.uploadAvatar.completion + 88 (ProfileViewModel.swift:72)
|
|
29
|
+
|
|
30
|
+
RELATED_CODE:
|
|
31
|
+
class ProfileViewModel {
|
|
32
|
+
var onUploadComplete: (() -> Void)?
|
|
33
|
+
|
|
34
|
+
func uploadAvatar(image: UIImage) {
|
|
35
|
+
let data = image.jpegData(compressionQuality: 0.8)! // line 67
|
|
36
|
+
uploadService.upload(data: data) { [unowned self] success in // line 72
|
|
37
|
+
if success {
|
|
38
|
+
self.onUploadComplete?()
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## What to Expect
|
|
48
|
+
|
|
49
|
+
The agent produces a full 9-section report. See [`example-output.md`](example-output.md).
|
|
50
|
+
|
|
51
|
+
**Root cause:** `[unowned self]` in an upload callback that fires after the ViewModel was deallocated. `unowned` does not nil-check before access — accessing freed memory causes `EXC_BAD_ACCESS`.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Variations
|
|
56
|
+
|
|
57
|
+
### Watchdog Termination (0x8badf00d)
|
|
58
|
+
```
|
|
59
|
+
USER_ACTION: App was in the background for 30 seconds uploading a video.
|
|
60
|
+
CRASH_LOG:
|
|
61
|
+
Exception Type: EXC_CRASH (SIGKILL)
|
|
62
|
+
Exception Codes: 0x0000000000000000, 0x0000000000000000
|
|
63
|
+
Exception Note: EXC_CORPSE_NOTIFY
|
|
64
|
+
Termination Reason: Namespace SPRINGBOARD, Code 0x8badf00d
|
|
65
|
+
Triggered by Thread: 0
|
|
66
|
+
```
|
|
67
|
+
Root cause: Background task not completing within 30 seconds; `setTaskCompleted()` not called in all code paths.
|
|
68
|
+
|
|
69
|
+
### Force-unwrap on optional chain
|
|
70
|
+
```
|
|
71
|
+
USER_ACTION: User navigated to the settings screen immediately after login.
|
|
72
|
+
CRASH_LOG:
|
|
73
|
+
Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
|
|
74
|
+
SocialApp ProfileCoordinator.showSettings() + 124 (ProfileCoordinator.swift:89)
|
|
75
|
+
SocialApp AppCoordinator.handleDeepLink(_:) + 56 (AppCoordinator.swift:142)
|
|
76
|
+
RELATED_CODE:
|
|
77
|
+
func showSettings() {
|
|
78
|
+
let vc = SettingsViewController()
|
|
79
|
+
vc.user = currentUser! // line 89
|
|
80
|
+
navigationController?.pushViewController(vc, animated: true)
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
Root cause: `currentUser` is nil because the user object hasn't been set yet when `showSettings()` is called from a deep link that arrives before the login flow completes.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Example Output — iOS Crash Analyzer
|
|
2
|
+
|
|
3
|
+
Full agent response to the input in [`example-input.md`](example-input.md).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Crash Summary
|
|
8
|
+
|
|
9
|
+
`EXC_BAD_ACCESS (SIGSEGV)` at `ProfileViewModel.swift:72` — the upload completion closure captures `self` with `[unowned self]`. The ViewModel was deallocated when the user dismissed the Profile screen, but the upload callback fired on the network thread afterward. Accessing a deallocated `unowned` reference is undefined behavior that always crashes. **CRITICAL** severity — reproducible whenever the user dismisses the screen while an upload is in flight (proportional to upload latency and user navigation speed).
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Root Cause
|
|
14
|
+
|
|
15
|
+
`[unowned self]` creates a non-optional, non-retaining reference to `self`. Unlike `[weak self]`, it does **not** nil-check before access — it simply assumes `self` is always alive when the closure runs.
|
|
16
|
+
|
|
17
|
+
Timeline of the crash:
|
|
18
|
+
1. User taps Upload → `uploadAvatar()` is called → `uploadService.upload(data:completion:)` starts async network work on Thread 3
|
|
19
|
+
2. User dismisses `ProfileViewController` → `ProfileViewController` releases its reference to `ProfileViewModel`
|
|
20
|
+
3. No other strong reference to `ProfileViewModel` exists → ARC deallocates it immediately
|
|
21
|
+
4. Upload completes on Thread 3 → the completion closure tries to access `self` via the `unowned` reference
|
|
22
|
+
5. The memory at that address has been freed → `swift_unknownObjectRetain` crashes with `KERN_INVALID_ADDRESS`
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Why This Happens
|
|
27
|
+
|
|
28
|
+
Think of `unowned` like a library card that promises the library will always exist. If the library closes (ViewModel deallocated), presenting the card crashes the system — there's nothing there anymore.
|
|
29
|
+
|
|
30
|
+
`weak` is the safe version: it's a membership that automatically becomes nil when the library closes. Checking `guard let self` before proceeding means if the library is gone, you simply walk away rather than crashing.
|
|
31
|
+
|
|
32
|
+
`unowned` is only safe when you can **guarantee** the captured object outlives the closure — for example, closures stored inside the same object, or in parent→child hierarchies where the parent always outlives the child. A network upload callback that can complete after the user navigates away is not that situation.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Risk Level
|
|
37
|
+
|
|
38
|
+
**CRITICAL**
|
|
39
|
+
Reproducibility: **Intermittent** — only when upload is in flight during dismissal
|
|
40
|
+
Frequency increases with: upload file size, network latency, back-navigation speed
|
|
41
|
+
Expected: top 5 Crashlytics issue on any app with image upload + navigation
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Recommended Fix
|
|
46
|
+
|
|
47
|
+
**Step 1:** Replace `[unowned self]` with `[weak self]` in the upload completion closure.
|
|
48
|
+
**Step 2:** Add `guard let self else { return }` to safely unwrap before any access.
|
|
49
|
+
**Step 3:** Remove the force-unwrap on `jpegData` — it can return nil.
|
|
50
|
+
**Step 4 (optional):** Cancel the in-flight upload when the ViewModel is deallocated by storing a reference to the upload task.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Updated Code
|
|
55
|
+
|
|
56
|
+
```swift
|
|
57
|
+
// Before — ProfileViewModel.swift
|
|
58
|
+
class ProfileViewModel {
|
|
59
|
+
var onUploadComplete: (() -> Void)?
|
|
60
|
+
|
|
61
|
+
func uploadAvatar(image: UIImage) {
|
|
62
|
+
let data = image.jpegData(compressionQuality: 0.8)! // crash if nil
|
|
63
|
+
uploadService.upload(data: data) { [unowned self] success in // CRASH: unowned + dealloc
|
|
64
|
+
if success {
|
|
65
|
+
self.onUploadComplete?()
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// After — ProfileViewModel.swift
|
|
72
|
+
class ProfileViewModel {
|
|
73
|
+
var onUploadComplete: (() -> Void)?
|
|
74
|
+
private var uploadTask: URLSessionTask?
|
|
75
|
+
|
|
76
|
+
func uploadAvatar(image: UIImage) {
|
|
77
|
+
// Guard: jpegData can return nil for certain image types or under memory pressure
|
|
78
|
+
guard let data = image.jpegData(compressionQuality: 0.8) else {
|
|
79
|
+
// notify caller of encoding failure
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
uploadTask = uploadService.upload(data: data) { [weak self] success in
|
|
84
|
+
// Safe: self is nil if ViewModel was deallocated — block exits cleanly
|
|
85
|
+
guard let self else { return }
|
|
86
|
+
if success {
|
|
87
|
+
self.onUploadComplete?()
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
deinit {
|
|
93
|
+
// Cancel the in-flight upload if the ViewModel is released (user navigated away)
|
|
94
|
+
uploadTask?.cancel()
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Edge Cases
|
|
102
|
+
|
|
103
|
+
- **Any other `[unowned self]` in escaping closures** in this class — audit all `uploadService`, `URLSession`, `Timer`, `NotificationCenter`, and `DispatchQueue.async` closures for the same pattern.
|
|
104
|
+
- **`onUploadComplete` itself** — if `onUploadComplete` captures a `UIViewController` strongly (e.g., to show an alert), and the VC is dismissed, calling it after dismiss may crash. Verify the block's capture list.
|
|
105
|
+
- **Background URLSession** — if using `URLSessionConfiguration.background`, completion is delivered to the `URLSessionDelegate`, not a closure. The delegate must hold a `weak` reference back to any object it notifies.
|
|
106
|
+
- **SwiftUI view that drives the upload** — if a `@StateObject` ViewModel hosts this logic, it is owned by the view hierarchy and lives as long as the view. The crash would not occur with `@StateObject` — but it would still occur with `@ObservedObject` if the parent is dismissed.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Testing Checklist
|
|
111
|
+
|
|
112
|
+
- [ ] Tap Upload → immediately tap Back → confirm **no crash** in Xcode console or Crashlytics
|
|
113
|
+
- [ ] Tap Upload on a throttled network (Network Link Conditioner → 3G) → tap Back → no crash
|
|
114
|
+
- [ ] Run the app with Address Sanitizer (Xcode scheme → Diagnostics → Address Sanitizer) → upload → navigate away → confirm no use-after-free report
|
|
115
|
+
- [ ] Verify in Instruments (Leaks) that `ProfileViewModel` is deallocated after screen dismissal — not retained by a leaked closure
|
|
116
|
+
- [ ] Confirm `uploadTask?.cancel()` is called in `deinit` by adding a `print("upload cancelled")` in `deinit` temporarily
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Prevention Tips
|
|
121
|
+
|
|
122
|
+
- **Default to `[weak self]`** for all escaping closures in network, timer, and notification callbacks. Use `[unowned self]` only in non-escaping closures or when you can document the lifetime guarantee in a comment.
|
|
123
|
+
- **Add a SwiftLint rule:** `unowned_variable_capture` — flags `unowned` in escaping closure capture lists.
|
|
124
|
+
- **Enable Address Sanitizer** in CI test runs — it catches use-after-free at test time before they reach production.
|
|
125
|
+
- **Migrate to async/await:** `async throws` functions structured with `Task { [weak self] in ... }` make capture semantics explicit and eliminate most completion-handler memory issues.
|
|
126
|
+
- **Code review checklist:** Any PR with a network or timer callback must document the capture list choice (`weak` vs `unowned` vs strong) and the reason.
|