Skill: ivangdavila/swift (version 1.0.1)

--- name: Swift slug: swift version: 1.0.1 description: Write safe Swift code avoiding memory leaks, optional traps, and concurrency bugs. metadata: {"clawdbot":{"emoji":"🦅","requires":{"bins":["swift"]},"os":["darwin","linux"]}} --- ## Quick Reference | Topic | File | |-------|------| | Optionals, nil safety, force unwrap | `optionals.md` | | Retain cycles, weak refs, closures | `memory.md` | | async/await, actors, Sendable, value types | `concurrency.md` | | JSON encoding/decoding traps | `codable.md` | | Protocols, collections, strings, errors, build | `types.md` | | SwiftUI state (@State, @Binding, Combine) | `swiftui.md` | | Property wrappers, actors, result builders, macros | `advanced.md` | | XCTest pitfalls, SPM gotchas | `testing.md` | ## Critical Rules ### Memory & Safety - Force unwrap `!` crashes on nil — use `guard let` or `if let` instead - Closures capturing `self` strongly create retain cycles — use `[weak self]` in escaping closures - Delegates must be `weak` — strong delegate = object never deallocates - `try!` crashes on any error — never use in production paths - `removeFirst()` crashes on empty — use `popFirst()` for safety ### Concurrency - `async let` starts immediately — not when you `await` - Actor reentrancy at every `await` — state may change between suspension points - `@MainActor` doesn't guarantee immediate main thread — it's queued - `Sendable` conformance violations crash at runtime — compiler warnings are errors ### Types & Collections - Protocol extensions don't override — static dispatch ignores subclass implementation - Mutating struct in collection requires reassignment — `array[0].mutate()` doesn't work - `String.Index` from one string invalid on another — even if contents match ### SwiftUI - `@StateObject` owns, `@ObservedObject` borrows — recreating view loses ObservedObject state - `@EnvironmentObject` crashes if not injected — no compile-time check - View identity change resets all `@State` — changing ID loses state ### Build - `print()` builds strings even in release — remove or use os_log - Generic code bloat — specialized for each type, increases binary size

path: advanced.md
content: # Property Wrappers Gotchas

- Wrapped value initialized before `self` available — can't access instance in property wrapper init
- `projectedValue` ($prefix) can be any type — read docs, it's not always a Binding
- `@propertyWrapper` needs `init(wrappedValue:)` — without it, can't use `= defaultValue` syntax
- Property wrappers break automatic Codable — add manual `CodingKeys` and `init(from:)`
- Structs with lazy property wrappers — copying struct recomputes lazy value

# Actors Advanced Traps

- `nonisolated` methods can't read actor-isolated stored properties — compiler error, use async method
- Actor reentrancy at every `await` — state may change between suspension points
- Protocol witnesses on global actors must be isolated — or explicitly `nonisolated`
- Distributed actors require all parameters/returns to be `Codable` and `Sendable`
- Actor-isolated closures can't escape to non-isolated contexts — use `@Sendable` and pass values

# Result Builders Traps

- `buildExpression` determines valid types — unclear errors if expression type not handled
- `buildOptional` required for bare `if` — without it, conditionals don't compile
- `buildEither(first:)` and `buildEither(second:)` required for `if-else`
- Complex type inference — builder methods order affects what compiles

# Macros (Swift 5.9+)

- Macro expansion errors point to usage site — actual bug is in macro definition
- Multiple attached macros can conflict — expansion order depends on declaration order
- Freestanding macros (`#macro`) produce expressions/declarations — attached (`@Macro`) modify declarations
- Macros operate on syntax only — can't inspect runtime values or types beyond what's written


path: codable.md
content: # Codable Pitfalls

- Missing key throws by default — use `decodeIfPresent` or custom init
- Type mismatch throws — `"123"` won't decode to `Int` automatically
- Enum raw value must match exactly — `"status": "ACTIVE"` fails for `.active` case
- Nested containers need manual `CodingKeys` at each level
- Custom `init(from:)` must decode ALL properties or provide defaults


path: concurrency.md
content: # Concurrency Traps

- `async let` starts immediately — not when you `await`
- Actor isolation: accessing actor property from outside requires `await` — even for reads
- `@MainActor` doesn't guarantee immediate main thread — it's queued
- `Task.detached` loses actor context — inherits nothing from caller
- Sendable conformance: mutable class properties violate thread safety silently until runtime crash

# Value vs Reference

- Structs copied on assign, classes shared — mutation affects only copy or all references
- Large structs copying is expensive — profile before assuming copy-on-write saves you
- Mutating struct in collection requires reassignment — `array[0].mutate()` doesn't work, extract, mutate, replace
- `inout` parameters: changes visible only after function returns — not during


path: memory.md
content: # Memory Leaks

- Closures capturing `self` strongly create retain cycles — use `[weak self]` in escaping closures
- Delegates must be `weak` — strong delegate = object never deallocates
- Timer retains target strongly — invalidate in `deinit` won't work, use `weak` or `block` API
- NotificationCenter observers retained until removed — remove in `deinit` or use `addObserver(forName:using:)` with token
- Nested closures: each level needs own `[weak self]` — inner closure captures outer's strong ref


path: optionals.md
content: # Optional Traps

- Force unwrap `!` crashes on nil — use `guard let` or `if let` instead
- Implicitly unwrapped optionals `String!` still crash if nil — only use for IBOutlets
- Optional chaining returns optional — `user?.name?.count` is `Int?` not `Int`
- `??` default value evaluates eagerly — use `?? { expensive() }()` for lazy
- Comparing optionals: `nil < 1` is true — unexpected sort behavior


path: swiftui.md
content: # SwiftUI State Traps

- `@State` must be private — shared state should use `@StateObject` or `@ObservedObject`
- `@StateObject` owns the object, `@ObservedObject` borrows — recreating view recreates ObservedObject
- `@Binding` with local state corrupts — don't derive Binding from @State in child then mutate both
- `@EnvironmentObject` crashes at runtime if not injected — no compile-time safety
- View identity determines state lifecycle — changing ID (explicit or implicit) resets all @State
- `ForEach` without `id:` or `Identifiable` conformance causes misrendering or crashes on mutation
- `GeometryReader` inside ScrollView breaks intrinsic sizing — use preference keys or overlay instead
- `onAppear` fires on every navigation return — not just initial appearance, use `task` with cancellation

# Combine Pitfalls

- Sink without storing `AnyCancellable` — subscription immediately cancelled, no values received
- `receive(on:)` after `sink` has no effect — must be before terminal subscriber
- `assign(to:on:)` creates strong reference to target — use `assign(to: &$published)` for @Published
- Memory leaks in custom Publishers — prefer `PassthroughSubject` or `CurrentValueSubject` over raw Publisher
- `eraseToAnyPublisher()` loses specific operators — chain everything before erasing
- Debugging Combine pipelines — insert `print("label")` operator to log all events


path: testing.md
content: # Testing Traps (XCTest)

- `XCTAssertEqual` with floating point — use `accuracy:` parameter for doubles
- Async test without expectation — test passes before async work completes
- `setUp()` vs `setUpWithError()` — latter throws, former swallows failures silently
- `@testable import` required for internal access — without it only public symbols visible
- Mock protocols need manual conformance — no automatic mocking, consider sourcery/swift-macro
- Test parallelization shares state — use `setUp` to reset, avoid static mutable state
- `XCTUnwrap` vs manual unwrap — prefer XCTUnwrap for clearer failure messages

# SPM Gotchas

- Version resolution conflicts — pin exact versions or use branch-based dependencies carefully
- Local package changes need `swift package resolve` — Xcode doesn't always auto-detect
- Platform-conditional dependencies use `#if` in targets — not in Package.swift directly
- Resources must be in `Package.swift` resources: — files in Sources/ not auto-bundled
- Plugins (build tools, commands) have sandboxed filesystem access — can't write anywhere
- Binary targets must specify exact checksums — updates require manifest changes


path: types.md
content: # Protocol Gotchas

- Protocol extensions don't override — static dispatch ignores subclass implementation
- `Self` requirement prevents use as type — `protocol Animal` vs `any Animal`
- `@objc` required for optional protocol methods
- Associated types can't use with `any` without constraints — use generics or type erasure
- Witness matching is exact — `func foo(_: Int)` doesn't satisfy `func foo(_: some Numeric)`

# String Traps

- Characters can be multiple Unicode scalars — emoji count isn't byte count
- Subscripting is O(n) — use indices, not integers
- `String.Index` from one string invalid on another — even if contents match
- Empty string is not nil — check `.isEmpty`, not `== nil`
- `contains()` is case-sensitive — use `localizedCaseInsensitiveContains` for user search

# Collection Edge Cases

- `first` and `last` are optional — empty collection returns nil
- `removeFirst()` crashes on empty, `popFirst()` returns nil
- `index(of:)` is O(n) — for frequent lookups use Set or Dictionary
- Mutating while iterating crashes — copy first or use `reversed()` for removal
- `ArraySlice` indices don't start at 0 — use `startIndex`

# Error Handling

- `try?` swallows error details — use only when error type doesn't matter
- `try!` crashes on any error — never use in production paths
- Throwing from closure requires explicit `throws` in closure type
- `rethrows` only works if closure throws — prevents unnecessary `try` at callsite
- Error must conform to `Error` — plain `throw "message"` doesn't compile

# Build and Runtime

- Generic code bloat — specialized for each type, increases binary size
- `@inlinable` exposes implementation to other modules — ABI stability consideration
- Dynamic casting `as?` can be slow — prefer static typing
- Reflection with `Mirror` is slow — not for hot paths
- `print()` builds strings even in release — remove or use os_log


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)