Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Builds iOS/macOS/watchOS/tvOS applications, implements SwiftUI views and state management, designs protocol-oriented architectures, handles async/await concurrency, implements actors for thread safety, and debugs Swift-specific issues. Use when building iOS/macOS applications with Swift 5.9+, SwiftUI, or async/await concurrency. Invoke for protocol-oriented programming, SwiftUI state management, actors, server-side Swift, UIKit integration, Combine, or Vapor.
.claude/skills/jeffallan-swift-expert/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | -21% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 14% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 44% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 28% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 63% | 0% |
> Validation checkpoints: After step 3, run swift build to verify compilation. After step 4, run swift build -warnings-as-errors to surface actor isolation and Sendable warnings. After step 5, run swift test and confirm all async tests pass.
Load detailed guidance based on context:
| Topic | Reference | Load When | |-------|-----------|-----------| | SwiftUI | references/swiftui-patterns.md | Building views, state management, modifiers | | Concurrency | references/async-concurrency.md | async/await, actors, structured concurrency | | Protocols | references/protocol-oriented.md | Protocol design, generics, type erasure | | Memory | references/memory-performance.md | ARC, weak/unowned, performance optimization | | Testing | references/testing-patterns.md | XCTest, async tests, mocking strategies |
swift// ✅ DO: async/await with structured error handling func fetchUser(id: String) async throws -> User { let url = URL(string: "https://api.example.com/users/\(id)")! let (data, _) = try await URLSession.shared.data(from: url) return try JSONDecoder().decode(User.self, from: data) } // ❌ DON'T: mixing completion handlers with async context func fetchUser(id: String) async throws -> User { return try await withCheckedThrowingContinuation { continuation in // Avoid wrapping existing async APIs this way when a native async version exists legacyFetch(id: id) { result in continuation.resume(with: result) } } }
swift// ✅ DO: use @Observable (Swift 5.9+) for view models @Observable final class CounterViewModel { var count = 0 func increment() { count += 1 } } struct CounterView: View { @State private var vm = CounterViewModel() var body: some View { VStack { Text("\(vm.count)") Button("Increment", action: vm.increment) } } } // ❌ DON'T: reach for ObservableObject/Published when @Observable suffices class LegacyViewModel: ObservableObject { @Published var count = 0 // Unnecessary boilerplate in Swift 5.9+ }
swift// ✅ DO: define capability protocols with associated types protocol Repository<Entity> { associatedtype Entity: Identifiable func fetch(id: Entity.ID) async throws -> Entity func save(_ entity: Entity) async throws } struct UserRepository: Repository { typealias Entity = User func fetch(id: UUID) async throws -> User { /* … */ } func save(_ user: User) async throws { /* … */ } } // ❌ DON'T: use classes as base types when a protocol fits class BaseRepository { // Avoid class inheritance for shared behavior func fetch(id: UUID) async throws -> Any { fatalError("Override required") } }
swift// ✅ DO: isolate mutable shared state in an actor actor ImageCache { private var cache: [URL: UIImage] = [:] func image(for url: URL) -> UIImage? { cache[url] } func store(_ image: UIImage, for url: URL) { cache[url] = image } } // ❌ DON'T: use a class with manual locking class UnsafeImageCache { private var cache: [URL: UIImage] = [:] private let lock = NSLock() // Error-prone; prefer actor isolation func image(for url: URL) -> UIImage? { lock.lock(); defer { lock.unlock() } return cache[url] } }
async/await for asynchronous operations (see pattern above)Sendable compliance for concurrencystruct/enum) by default/// …)!) without justificationWhen implementing Swift features, provide:
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 26,345 | 15,410 | -42% | 1 | 1 | 0% | 6,187 | 4,913 | -21% | 0 | 0 | — |
case-02 | pass→pass | 25,197 | 20,132 | -20% | 1 | 1 | 0% | 5,298 | 6,031 | +14% | 0 | 0 | — |
case-03 | pass→pass | 15,498 | 15,501 | +0% | 1 | 1 | 0% | 3,293 | 4,730 | +44% | 0 | 0 | — |
case-04 | pass→pass | 14,516 | 11,107 | -23% | 1 | 1 | 0% | 2,834 | 3,617 | +28% | 0 | 0 | — |
case-05 | pass→pass | 10,390 | 8,855 | -15% | 1 | 1 | 0% | 1,814 | 2,952 | +63% | 0 | 0 | — |
case-06 | pass→pass | 16,774 | 19,403 | +16% | 1 | 1 | 0% | 3,508 | 5,591 | +59% | 0 | 0 | — |
case-07 | pass→pass | 18,226 | 20,763 | +14% | 1 | 1 | 0% | 3,743 | 5,290 | +41% | 0 | 0 | — |
case-08 | pass→pass | 8,403 | 9,532 | +13% | 1 | 1 | 0% | 1,641 | 3,295 | +101% | 0 | 0 | — |
case-09 | pass→pass | 11,955 | 16,263 | +36% | 1 | 1 | 0% | 2,254 | 4,188 | +86% | 0 | 0 | — |
case-10 | pass→pass | 4,596 | 3,058 | -33% | 1 | 1 | 0% | 855 | 1,783 | +109% | 0 | 0 | — |
case-11 | pass→pass | 12,897 | 12,231 | -5% | 1 | 1 | 0% | 2,565 | 3,570 | +39% | 0 | 0 | — |
case-12 | pass→pass | 8,626 | 10,661 | +24% | 1 | 1 | 0% | 1,813 | 3,373 | +86% | 0 | 0 | — |
case-13 | fail→fail | 8,198 | 11,859 | +45% | 1 | 1 | 0% | 1,586 | 3,682 | +132% | 0 | 0 | — |
case-14 | pass→pass | 10,425 | 11,275 | +8% | 1 | 1 | 0% | 1,848 | 3,650 | +98% | 0 | 0 | — |
case-15 | pass→pass | 14,472 | 14,362 | -1% | 1 | 1 | 0% | 2,826 | 4,067 | +44% | 0 | 0 | — |
case-16 | pass→pass | 7,337 | 7,450 | +2% | 1 | 1 | 0% | 1,307 | 2,735 | +109% | 0 | 0 | — |
case-17 | pass→pass | 11,718 | 13,245 | +13% | 1 | 1 | 0% | 2,240 | 3,841 | +71% | 0 | 0 | — |
case-18 | pass→pass | 13,283 | 11,464 | -14% | 1 | 1 | 0% | 2,866 | 3,734 | +30% | 0 | 0 | — |
case-19 | pass→pass | 20,687 | 16,375 | -21% | 1 | 1 | 0% | 4,057 | 4,650 | +15% | 0 | 0 | — |
case-20 | pass→pass | 12,104 | 9,676 | -20% | 1 | 1 | 0% | 2,295 | 3,083 | +34% | 0 | 0 | — |
case-21 | pass→pass | 5,671 | 5,436 | -4% | 1 | 1 | 0% | 1,193 | 2,373 | +99% | 0 | 0 | — |
case-22 | pass→pass | 10,546 | 13,053 | +24% | 1 | 1 | 0% | 2,207 | 3,846 | +74% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +5 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.