# Ios Performance

> >

- **Type:** Skill
- **Install:** `agentstack add skill-koshkinvv-ios-agent-skills-ios-performance`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [koshkinvv](https://agentstack.voostack.com/s/koshkinvv)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [koshkinvv](https://github.com/koshkinvv)
- **Source:** https://github.com/koshkinvv/ios-agent-skills/tree/main/skills/ios-performance

## Install

```sh
agentstack add skill-koshkinvv-ios-agent-skills-ios-performance
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# iOS Performance Optimization Skill

## Core Rules

1. **Use `[weak self]` in escaping closures by default** — use `[unowned self]` only when the closure's lifetime is strictly shorter than the captured object (e.g., parent owns child, child's closure references parent).
2. **Non-escaping closures do NOT need `[weak self]`** — `map`, `filter`, `forEach`, `reduce`, `compactMap`, `sorted(by:)` are non-escaping. The closure executes synchronously and releases captures immediately.
3. **Delegates must be `weak var`** — the delegate protocol must conform to `AnyObject` (or be marked `@objc`). Strong delegates create retain cycles between owner and delegate.
4. **Use `@Observable` over `ObservableObject`** (iOS 17+) — `@Observable` tracks property access per-view, so only views reading a changed property re-evaluate. `ObservableObject` with `@Published` invalidates ALL observing views on ANY published property change.
5. **Break SwiftUI views into small subviews** — each subview localizes its state dependency, so changes only re-evaluate the subview, not the entire parent body.
6. **Use `LazyVStack`/`LazyHStack` inside `ScrollView` for large collections** — `VStack` evaluates ALL children upfront. Use `List` for very large datasets (10,000+) since it reuses cells.
7. **Use `.task` modifier instead of `.onAppear` + `Task {}`** — `.task` automatically cancels when the view disappears. Use `.task(id:)` to restart when a dependency changes.
8. **Profile with Instruments BEFORE optimizing** — measure first, optimize second. Never guess where the bottleneck is.
9. **Never block the main thread** — all file I/O, network calls, JSON decoding of large payloads, image processing, and database queries must run off the main thread.
10. **Reuse expensive objects** — `URLSession`, `DateFormatter`, `JSONDecoder`, `NumberFormatter`, `NSRegularExpression` are expensive to create. Use shared instances or caches.

## Performance Targets

| Metric | Target | Critical Threshold |
|--------|--------|--------------------|
| Cold launch | 2s triggers watchdog kill |
| Warm launch | 1s feels broken |
| Frame rate | 60 FPS (120 on ProMotion) | 33ms = visible dropped frame |
| Memory (typical) | 500MB risks jetsam on older devices |
| Memory (spike) | 5% drains battery |
| CPU active task | 1s needs loading indicator |
| App size (download) | 200MB requires Wi-Fi |
| Disk writes |  Void = { [unowned self] in
        self.doSomething()  // Parent always outlives its own lazy property
    }
}

// NOT NEEDED: Non-escaping closure — no capture cycle possible
let names = users.map { $0.name }          // map is non-escaping
let adults = users.filter { $0.age >= 18 } // filter is non-escaping
items.forEach { print($0) }                // forEach is non-escaping

// CORRECT: Weak delegate
protocol DataServiceDelegate: AnyObject {
    func didUpdate(_ data: Data)
}

class DataService {
    weak var delegate: DataServiceDelegate?
}
```

## SwiftUI Performance Quick Reference

```swift
// BAD: One large view — any state change re-evaluates entire body
struct ProfileView: View {
    @State private var name = ""
    @State private var bio = ""
    @State private var avatarURL: URL?
    @State private var posts: [Post] = []

    var body: some View {
        ScrollView {
            avatarSection     // Change to name re-evaluates avatar too
            bioSection
            postsSection      // All 500 post cells re-evaluated
        }
    }
}

// GOOD: Extracted subviews — state changes localized
struct ProfileView: View {
    var body: some View {
        ScrollView {
            AvatarSection()   // Only re-evaluates when avatar changes
            BioSection()      // Only re-evaluates when bio changes
            PostsSection()    // Only re-evaluates when posts change
        }
    }
}

// GOOD: LazyVStack for scrollable content
struct PostsSection: View {
    let posts: [Post]

    var body: some View {
        LazyVStack {  // Only creates visible cells + prefetch buffer
            ForEach(posts) { post in
                PostRow(post: post)
            }
        }
    }
}

// GOOD: .task with auto-cancellation
struct UserDetailView: View {
    let userID: String
    @State private var user: User?

    var body: some View {
        content
            .task(id: userID) {  // Cancels & restarts if userID changes
                user = try? await api.fetchUser(userID)
            }
    }
}
```

## @Observable vs ObservableObject

```swift
// OLD (iOS 14+): ObservableObject — ALL views re-evaluate on ANY change
class UserViewModel: ObservableObject {
    @Published var name = ""       // Change triggers ALL observers
    @Published var email = ""      // Change triggers ALL observers
    @Published var avatarURL: URL? // Change triggers ALL observers
}

// NEW (iOS 17+): @Observable — only views reading changed property re-evaluate
@Observable
class UserViewModel {
    var name = ""       // Only views reading `name` re-evaluate
    var email = ""      // Only views reading `email` re-evaluate
    var avatarURL: URL? // Only views reading `avatarURL` re-evaluate
}
```

## Instruments Workflow

### Step 1: Profile, Don't Debug
Always profile on a **real device** (not Simulator). Use **Release** configuration for accurate measurements.

### Step 2: Choose the Right Instrument
- **Time Profiler** — CPU bottlenecks, main thread blocking
- **Allocations** — memory growth, leaks, allocation hotspots
- **Leaks** — automatic retain cycle detection (periodic snapshots)
- **Memory Graph Debugger** (Xcode, not Instruments) — visual reference chains
- **Core Animation** — rendering performance, blended layers, offscreen rendering
- **Energy Log** — battery drain causes (CPU, network, GPS, Bluetooth)
- **Network** — HTTP request/response analysis
- **App Launch** — cold/warm launch breakdown
- **SwiftUI** (Xcode 16+) — view body evaluations, cause & effect graph

### Step 3: Time Profiler Settings (Always Set These)
1. **Invert Call Tree** — shows heaviest leaf functions first
2. **Separate by Thread** — isolates main thread work
3. **Hide System Libraries** — focuses on your code
4. **Separate by State** — shows running vs blocked time

### Step 4: Record, Reproduce, Analyze
1. Record for 10-30 seconds covering the problematic interaction
2. Select the time range of interest
3. Look at the heaviest stack traces
4. Focus on main thread first (Thread 1)

## App Launch Optimization Checklist

### Pre-main Phase (<200ms target)
- [ ] Max 6 non-system dynamic frameworks (each adds ~10-20ms)
- [ ] No `+load` methods in ObjC code (move to `+initialize` or lazy init)
- [ ] Minimize static initializers (C++ globals, `__attribute__((constructor))`)
- [ ] Use static linking where possible (SPM default)

### Post-main Phase (<200ms target)
- [ ] Defer non-essential initialization (analytics, logging, feature flags)
- [ ] Use `lazy var` for expensive properties
- [ ] Load first screen data from cache, then refresh from network
- [ ] Avoid synchronous network calls at launch
- [ ] Minimize work in `application(_:didFinishLaunchingWithOptions:)`
- [ ] Use `Scene` phase detection instead of heavy AppDelegate setup

## Common Anti-Patterns

### 1. Main Thread Blocking
```swift
// BAD: Synchronous file read on main thread
let data = try! Data(contentsOf: largeFileURL)

// GOOD: Async file read
let data = try await Task.detached {
    try Data(contentsOf: largeFileURL)
}.value
```

### 2. Excessive Allocations in Loops
```swift
// BAD: Creates new DateFormatter per iteration (expensive!)
for event in events {
    let formatter = DateFormatter()
    formatter.dateFormat = "yyyy-MM-dd"
    labels.append(formatter.string(from: event.date))
}

// GOOD: Reuse formatter
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
for event in events {
    labels.append(formatter.string(from: event.date))
}
```

### 3. VStack for Large Collections
```swift
// BAD: Creates ALL 10,000 views upfront
ScrollView {
    VStack {
        ForEach(items) { item in  // 10,000 items = 10,000 views in memory
            ItemRow(item: item)
        }
    }
}

// GOOD: Only creates visible views
ScrollView {
    LazyVStack {
        ForEach(items) { item in  // ~20 views in memory at a time
            ItemRow(item: item)
        }
    }
}
```

### 4. Retaining Self in Timers
```swift
// BAD: Timer retains self, self retains timer → cycle
class PollingService {
    var timer: Timer?

    func start() {
        timer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { _ in
            self.poll()  // Strong capture → retain cycle
        }
    }
}

// GOOD: Weak capture
func start() {
    timer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { [weak self] _ in
        self?.poll()
    }
}
```

### 5. Not Cancelling Tasks
```swift
// BAD: Task keeps running after view disappears
.onAppear {
    Task {
        while !Task.isCancelled {
            await refresh()
            try await Task.sleep(for: .seconds(30))
        }
    }
}

// GOOD: .task auto-cancels on disappear
.task {
    while !Task.isCancelled {
        await refresh()
        try? await Task.sleep(for: .seconds(30))
    }
}
```

## Reference Files

For deep dives, see:
- `references/memory.md` — ARC, retain cycles, value vs reference types, copy-on-write, debugging
- `references/swiftui-perf.md` — View identity, @Observable, lazy containers, images, .task
- `references/instruments.md` — Time Profiler, Allocations, Leaks, Energy, Core Animation, SwiftUI instrument
- `references/optimization.md` — App launch, network, battery, build performance, anti-patterns

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [koshkinvv](https://github.com/koshkinvv)
- **Source:** [koshkinvv/ios-agent-skills](https://github.com/koshkinvv/ios-agent-skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-koshkinvv-ios-agent-skills-ios-performance
- Seller: https://agentstack.voostack.com/s/koshkinvv
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
