app-lifecycle
Expert lifecycle decisions for iOS/tvOS: when SwiftUI lifecycle vs SceneDelegate, background task strategies, state restoration trade-offs, and launch optimization. Use when managing app state transitions, handling background work, or debugging lifecycle issues. Trigger keywords: lifecycle, scenePhase, SceneDelegate, AppDelegate, background task, state restoration, launch time, didFinishLaunching, applicationWillTerminate, sceneDidBecomeActive
Best use case
app-lifecycle is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Expert lifecycle decisions for iOS/tvOS: when SwiftUI lifecycle vs SceneDelegate, background task strategies, state restoration trade-offs, and launch optimization. Use when managing app state transitions, handling background work, or debugging lifecycle issues. Trigger keywords: lifecycle, scenePhase, SceneDelegate, AppDelegate, background task, state restoration, launch time, didFinishLaunching, applicationWillTerminate, sceneDidBecomeActive
Teams using app-lifecycle should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/app-lifecycle/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How app-lifecycle Compares
| Feature / Agent | app-lifecycle | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Expert lifecycle decisions for iOS/tvOS: when SwiftUI lifecycle vs SceneDelegate, background task strategies, state restoration trade-offs, and launch optimization. Use when managing app state transitions, handling background work, or debugging lifecycle issues. Trigger keywords: lifecycle, scenePhase, SceneDelegate, AppDelegate, background task, state restoration, launch time, didFinishLaunching, applicationWillTerminate, sceneDidBecomeActive
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# App Lifecycle — Expert Decisions
Expert decision frameworks for app lifecycle choices. Claude knows scenePhase and SceneDelegate — this skill provides judgment calls for architecture decisions and background task trade-offs.
---
## Decision Trees
### Lifecycle API Selection
```
What's your project setup?
├─ Pure SwiftUI app (iOS 14+)
│ └─ @main App + scenePhase
│ Simplest approach, sufficient for most apps
│
├─ Need UIKit integration
│ └─ SceneDelegate + UIHostingController
│ Required for some third-party SDKs
│
├─ Need pre-launch setup
│ └─ AppDelegate + SceneDelegate
│ SDK initialization, remote notifications
│
└─ Legacy app (pre-iOS 13)
└─ AppDelegate only
window property on AppDelegate
```
**The trap**: Using SceneDelegate when pure SwiftUI suffices. scenePhase covers most use cases without the boilerplate.
### Background Task Strategy
```
What work needs to happen in background?
├─ Quick save (< 5 seconds)
│ └─ UIApplication.beginBackgroundTask
│ Request extra time in sceneDidEnterBackground
│
├─ Network sync (< 30 seconds)
│ └─ BGAppRefreshTask
│ System schedules, best-effort timing
│
├─ Large download/upload
│ └─ Background URL Session
│ Continues even after app termination
│
├─ Location tracking
│ └─ Location background mode
│ Significant change or continuous
│
└─ Long processing (> 30 seconds)
└─ BGProcessingTask
Runs during charging, overnight
```
### State Restoration Approach
```
What state needs restoration?
├─ Simple navigation state
│ └─ @SceneStorage
│ Per-scene, automatic, Codable types only
│
├─ Complex navigation + data
│ └─ @AppStorage + manual encoding
│ More control, cross-scene sharing
│
├─ UIKit-based navigation
│ └─ State restoration identifiers
│ encodeRestorableState/decodeRestorableState
│
└─ Don't need restoration
└─ Start fresh each launch
Some apps are better this way
```
### Launch Optimization Priority
```
What's blocking your launch time?
├─ SDK initialization
│ └─ Defer non-critical SDKs
│ Analytics can wait, auth cannot
│
├─ Database loading
│ └─ Lazy loading + skeleton UI
│ Show UI immediately, load data async
│
├─ Network requests
│ └─ Cache + background refresh
│ Never block launch for network
│
└─ Asset loading
└─ Progressive loading
Load visible content first
```
---
## NEVER Do
### Launch Time
**NEVER** block main thread during launch:
```swift
// ❌ UI frozen until network completes
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let data = try! Data(contentsOf: remoteURL) // Synchronous network!
processData(data)
return true
}
// ✅ Defer non-critical work
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
setupCriticalServices() // Auth, crash reporting
Task.detached(priority: .background) {
await self.setupNonCriticalServices() // Analytics, prefetch
}
return true
}
```
**NEVER** initialize all SDKs synchronously:
```swift
// ❌ Each SDK adds to launch time
func application(...) -> Bool {
AnalyticsSDK.initialize() // 100ms
CrashReporterSDK.initialize() // 50ms
FeatureFlagsSDK.initialize() // 200ms
SocialSDK.initialize() // 150ms
// Total: 500ms added to launch!
return true
}
// ✅ Prioritize and defer
func application(...) -> Bool {
CrashReporterSDK.initialize() // Critical — catches launch crashes
DispatchQueue.main.async {
AnalyticsSDK.initialize() // Can wait one runloop
}
Task.detached(priority: .utility) {
FeatureFlagsSDK.initialize()
SocialSDK.initialize()
}
return true
}
```
### Background Tasks
**NEVER** assume background time is guaranteed:
```swift
// ❌ May not complete — iOS can terminate anytime
func sceneDidEnterBackground(_ scene: UIScene) {
performLongSync() // No protection!
}
// ✅ Request background time and handle expiration
func sceneDidEnterBackground(_ scene: UIScene) {
var taskId: UIBackgroundTaskIdentifier = .invalid
taskId = UIApplication.shared.beginBackgroundTask {
// Expiration handler — save partial progress
savePartialProgress()
UIApplication.shared.endBackgroundTask(taskId)
}
Task {
await performSync()
UIApplication.shared.endBackgroundTask(taskId)
}
}
```
**NEVER** forget to end background tasks:
```swift
// ❌ Leaks background task — iOS may terminate app
func saveData() {
let taskId = UIApplication.shared.beginBackgroundTask { }
saveToDatabase()
// Missing: endBackgroundTask!
}
// ✅ Always end in both success and failure
func saveData() {
var taskId: UIBackgroundTaskIdentifier = .invalid
taskId = UIApplication.shared.beginBackgroundTask {
UIApplication.shared.endBackgroundTask(taskId)
}
defer { UIApplication.shared.endBackgroundTask(taskId) }
do {
try saveToDatabase()
} catch {
Logger.app.error("Save failed: \(error)")
}
}
```
### State Transitions
**NEVER** trust applicationWillTerminate to be called:
```swift
// ❌ May never be called — iOS can kill app without notice
func applicationWillTerminate(_ application: UIApplication) {
saveCriticalData() // Not guaranteed to run!
}
// ✅ Save on every background transition
func sceneDidEnterBackground(_ scene: UIScene) {
saveCriticalData() // Called reliably
}
// Also save periodically during use
Timer.scheduledTimer(withTimeInterval: 60, repeats: true) { _ in
saveApplicationState()
}
```
**NEVER** do heavy work in sceneWillResignActive:
```swift
// ❌ Blocks app switcher animation
func sceneWillResignActive(_ scene: UIScene) {
generateThumbnails() // Visible lag in app switcher
syncToServer() // Delays user
}
// ✅ Only pause essential operations
func sceneWillResignActive(_ scene: UIScene) {
pauseVideoPlayback()
pauseAnimations()
// Heavy work goes in sceneDidEnterBackground
}
```
### Scene Lifecycle
**NEVER** confuse scene disconnect with app termination:
```swift
// ❌ Wrong assumption
func sceneDidDisconnect(_ scene: UIScene) {
// App is terminating! <- WRONG
cleanupEverything()
}
// ✅ Scene disconnect means scene released, not app death
func sceneDidDisconnect(_ scene: UIScene) {
// Scene being released — save per-scene state
// App may continue running with other scenes
// Or system may reconnect this scene later
saveSceneState(scene)
}
```
---
## Essential Patterns
### SwiftUI Lifecycle Handler
```swift
@main
struct MyApp: App {
@Environment(\.scenePhase) private var scenePhase
@StateObject private var appState = AppState()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(appState)
}
.onChange(of: scenePhase) { oldPhase, newPhase in
handlePhaseChange(from: oldPhase, to: newPhase)
}
}
private func handlePhaseChange(from old: ScenePhase, to new: ScenePhase) {
switch (old, new) {
case (_, .active):
appState.refreshDataIfStale()
case (.active, .inactive):
// Transitioning away — pause but don't save yet
appState.pauseActiveOperations()
case (_, .background):
appState.saveState()
scheduleBackgroundRefresh()
default:
break
}
}
}
```
### Background Task Manager
```swift
final class BackgroundTaskManager {
static let shared = BackgroundTaskManager()
func registerTasks() {
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.app.refresh",
using: nil
) { task in
self.handleAppRefresh(task as! BGAppRefreshTask)
}
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.app.processing",
using: nil
) { task in
self.handleProcessing(task as! BGProcessingTask)
}
}
func scheduleRefresh() {
let request = BGAppRefreshTaskRequest(identifier: "com.app.refresh")
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
try? BGTaskScheduler.shared.submit(request)
}
private func handleAppRefresh(_ task: BGAppRefreshTask) {
scheduleRefresh() // Schedule next refresh
let refreshTask = Task {
await performRefresh()
}
task.expirationHandler = {
refreshTask.cancel()
}
Task {
await refreshTask.value
task.setTaskCompleted(success: true)
}
}
}
```
### Launch Time Optimization
```swift
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
private var launchStartTime: CFAbsoluteTime = 0
func application(_ application: UIApplication,
willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
launchStartTime = CFAbsoluteTimeGetCurrent()
// Phase 1: Absolute minimum (crash reporting)
CrashReporter.initialize()
return true
}
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Phase 2: Required for first frame
configureAppearance()
// Phase 3: Deferred to after first frame
DispatchQueue.main.async {
self.completePostLaunchSetup()
let launchTime = CFAbsoluteTimeGetCurrent() - self.launchStartTime
Logger.app.info("Launch completed in \(launchTime)s")
}
return true
}
private func completePostLaunchSetup() {
// Analytics, feature flags, etc.
Task.detached(priority: .utility) {
Analytics.initialize()
FeatureFlags.refresh()
}
}
}
```
---
## Quick Reference
### Lifecycle Events Order
| Event | When | Use For |
|-------|------|---------|
| willFinishLaunching | Before UI | Crash reporting only |
| didFinishLaunching | UI ready | Critical setup |
| sceneWillEnterForeground | Coming to front | Undo background changes |
| sceneDidBecomeActive | Fully active | Refresh, restart tasks |
| sceneWillResignActive | Losing focus | Pause playback |
| sceneDidEnterBackground | In background | Save state, start bg task |
| sceneDidDisconnect | Scene released | Save scene state |
### Background Task Limits
| Task Type | Time Limit | When Runs |
|-----------|-----------|-----------|
| beginBackgroundTask | ~30 seconds | Immediately |
| BGAppRefreshTask | ~30 seconds | System discretion |
| BGProcessingTask | Minutes | Charging, overnight |
| Background URL Session | Unlimited | System managed |
### State Restoration Options
| Approach | Scope | Types | Auto-save |
|----------|-------|-------|-----------|
| @SceneStorage | Per-scene | Codable | Yes |
| @AppStorage | App-wide | Primitives | Yes |
| Restoration ID | Per-VC | Custom | Manual |
### Red Flags
| Smell | Problem | Fix |
|-------|---------|-----|
| Sync network in launch | Blocked UI | Async + skeleton UI |
| All SDKs in didFinish | Slow launch | Prioritize + defer |
| No beginBackgroundTask | Work may not complete | Always request time |
| Missing endBackgroundTask | Leaked task | Use defer |
| Heavy work in willResignActive | Laggy app switcher | Move to didEnterBackground |
| Trust applicationWillTerminate | May not be called | Save on background |
| Confuse sceneDidDisconnect | Scene != app termination | Save scene state only |Related Skills
api-lifecycle
Provides API design and lifecycle management guidance including versioning, deprecation, rate limiting, and documentation standards. Triggers on api design, rest api, graphql, api versioning, deprecation, breaking changes, rate limiting, api lifecycle, api documentation, openapi.
cfn-agent-lifecycle
Unified agent management from selection through completion - spawning, execution, output processing. Use when selecting agents for tasks, spawning agents with dependency validation, processing agent outputs, or tracking agent lifecycle events with audit trails.
agent-lifecycle-management
Manage agent fleet through CRUD operations and lifecycle patterns. Use when creating, commanding, monitoring, or deleting agents in multi-agent systems, or implementing proper resource cleanup.
bgo
Automates the complete Blender build-go workflow, from building and packaging your extension/add-on to removing old versions, installing, enabling, and launching Blender for quick testing and iteration.
architecture-documentation-creator
Create comprehensive technical documentation for code systems including data flow diagrams, architecture overviews, algorithm documentation, cheat sheets, and multi-file documentation sets. Use when documenting pipelines, algorithms, system architecture, data flow, multi-stage processes, similarity algorithms, or creating developer onboarding materials. Covers Mermaid diagrams, progressive disclosure, critical patterns, JSON schemas, Pydantic models, and print-friendly reference materials.
architecture-docs
Create and maintain architecture documentation with Mermaid diagrams. Use when writing technical documentation, system diagrams, or updating the implementation plan.
architecture-discovery
Guide users through discovering and defining system architecture through structured conversation. Triggers on "I want to build", "design a system", "architect", "planning a new project", "how should I build X".
architecture-diagrams
This skill should be used when the user asks to "create a diagram", "draw architecture", "make a Mermaid diagram", "update the system diagram", "visualize data flow", or when generating flowcharts, module diagrams, or dependency graphs. Provides standards for clear, unambiguous Mermaid diagrams.
Architecture Diagramming Expert
Create professional architecture diagrams using D2, Draw.io, Mermaid, and OCI official icons for enterprise-grade visualizations
architecture-diagram
Generate architecture diagrams in Mermaid, PlantUML, or diagrams.net formats
architecture-diagram-creator
Create comprehensive HTML architecture diagrams showing data flows, business objectives, features, technical architecture, and deployment. Use when users request system architecture, project documentation, high-level overviews, or technical specifications.
architecture-designer
Use when designing new system architecture, reviewing existing designs, or making architectural decisions. Invoke for system design, architecture review, design patterns, ADRs, scalability planning.