AgentStack
SKILL verified MIT Self-run

Ios Performance

skill-koshkinvv-ios-agent-skills-ios-performance · by koshkinvv

>

No reviews yet
0 installs
14 views
0.0% view→install

Install

$ agentstack add skill-koshkinvv-ios-agent-skills-ios-performance

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Ios Performance? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

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 collectionsVStack 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 objectsURLSession, 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

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

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

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

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

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

// 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.

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

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.