/swift-patterns
Swift/iOS: SwiftUI, Combine, async/await, actors, SPM, Core Data, UIKit interop. Triggers: Swift, SwiftUI, Combine, iOS, Xcode, actor, Core Data, @MainActor, @State.
$ npx -y skills add softspark/ai-toolkit --skill swift-patterns --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/swift-patterns
Context preview
The summary Claude sees to decide when to auto-load this skill.
Swift/iOS: SwiftUI, Combine, async/await, actors, SPM, Core Data, UIKit interop. Triggers: Swift, SwiftUI, Combine, iOS, Xcode, actor, Core Data, @MainActor, @State.
SKILL.md
swift-patterns.SKILL.mdname: swift-patterns
description: "Swift/iOS: SwiftUI, Combine, async/await, actors, SPM, Core Data, UIKit interop. Triggers: Swift, SwiftUI, Combine, iOS, Xcode, actor, Core Data, @MainActor, @State."
effort: medium
user-invocable: false
allowed-tools: Read
Swift / iOS Patterns
Project Structure
Swift Package (SPM)
MyPackage/
├── Package.swift
├── Sources/
│ ├── MyLibrary/
│ └── MyExecutable/
├── Tests/
│ └── MyLibraryTests/
└── Plugins/
// swift-tools-version: 5.10
import PackageDescription
let package = Package(
name: "MyPackage",
platforms: [.iOS(.v17), .macOS(.v14)],
products: [
.library(name: "MyLibrary", targets: ["MyLibrary"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-algorithms", from: "1.2.0"),
],
targets: [
.target(name: "MyLibrary",
dependencies: [.product(name: "Algorithms", package: "swift-algorithms")]),
.testTarget(name: "MyLibraryTests", dependencies: ["MyLibrary"]),
]
)Xcode Project Layout
MyApp/
├── MyApp/
│ ├── App/ # Entry point, ContentView
│ ├── Features/ # Feature modules (Views, ViewModels, Models)
│ ├── Core/ # Networking, Storage, Extensions
│ └── Resources/ # Assets.xcassets, Info.plist
├── MyAppTests/
└── MyAppUITests/
---
Idioms / Code Style
Optionals
// guard-let for early exit
func process(user: User?) {
guard let user else { return }
print(user.name)
}
// Optional chaining + nil coalescing
let name = user?.profile?.displayName ?? "Anonymous"
// map/flatMap on optionals
let length: Int? = optionalString.map { $0.count }
// Never force-unwrap in production: user!.nameProtocol-Oriented Programming
protocol Cacheable: Identifiable where ID: Hashable {
var cacheKey: String { get }
}
extension Cacheable where ID == String {
var cacheKey: String { id }
}Value Types vs Reference Types
Use structs by default (value semantics, thread-safe). Use classes when identity matters, shared mutable state is intentional, inheritance is needed, or ObjC interop is required.
Property Wrappers
@propertyWrapper
struct Clamped<Value: Comparable> {
var wrappedValue: Value {
didSet { wrappedValue = min(max(wrappedValue, range.lowerBound), range.upperBound) }
}
let range: ClosedRange<Value>
init(wrappedValue: Value, _ range: ClosedRange<Value>) {
self.range = range
self.wrappedValue = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
}
struct Volume {
@Clamped(0...100) var level: Int = 50
}Result Builders
@resultBuilder
struct ArrayBuilder<Element> {
static func buildBlock(_ components: [Element]...) -> [Element] { components.flatMap { $0 } }
static func buildExpression(_ expression: Element) -> [Element] { [expression] }
static func buildOptional(_ component: [Element]?) -> [Element] { component ?? [] }
}---
Error Handling
throws / try / catch
enum NetworkError: Error, LocalizedError {
case invalidURL
case timeout(seconds: Int)
case serverError(statusCode: Int)
var errorDescription: String? {
switch self {
case .invalidURL: "Invalid URL."
case .timeout(let s): "Timed out after \(s)s."
case .serverError(let code): "Server returned \(code)."
}
}
}
do {
let user = try fetchUser(id: "123")
} catch let error as NetworkError {
handleNetworkError(error)
} catch {
handleUnexpected(error)
}
let user = try? fetchUser(id: "123") // nil on errorResult Type
switch fetchData(from: url) {
case .success(let data): process(data)
case .failure(let error): showError(error)
}Typed Throws (Swift 6) and Async Throws
func load() throws(DatabaseError) -> [Item] { /* compiler-enforced error type */ }
func fetchUser(id: String) async throws -> User {
let (data, response) = try await URLSession.shared.data(from: url)
guard let http = response as? HTTPURLResponse, http.statusCode == 200 else {
throw NetworkError.serverError(statusCode: 0)
}
return try JSONDecoder().decode(User.self, from: data)
}---
Testing
XCTest
final class UserServiceTests: XCTestCase {
var sut: UserService!
var mockRepo: MockUserRepository!
override func setUp() {
mockRepo = MockUserRepository()
sut = UserService(repository: mockRepo)
}
func testFetchUser_success() async throws {
mockRepo.stubbedUser = User(id: "1", name: "Alice")
let user = try await sut.fetchUser(id: "1")
XCTAssertEqual(user.name, "Alice")
}
func testFetchUser_notFound_throws() async {
mockRepo.shouldFail = true
do {
_ = try await sut.fetchUser(id: "999")
XCTFail("Expected error")
} catch {
XCTAssertTrue(error is UserService.Error)
}
}
}Swift Testing Framework (Swift 6+)
import Testing
@Suite("UserService")
struct UserServiceTests {
@Test("fetches user by ID")
func fetchUser() async throws {
let mockRepo = MockUserRepository()
mockRepo.stubbedUser = User(id: "1", name: "Alice")
let sut = UserService(repository: mockRepo)
let user = try await sut.fetchUser(id: "1")
#expect(user.name == "Alice")
}
@Test("throws on missing user", arguments: ["999", ""])
func fetchMissingUser(id: String) async {
let sut = UserService(repository: MockUserRepository())
await #expect(throws: UserService.Error.self) {
try await sut.fetchUser(id: id)
}
}
}Protocol-Based Mocking
protocol UserRepository {
func fetch(id: String) async throws -> User
}
final class MockUserRepository: UserRepository {
var stubbedUser: User?
var shouldFailRead more
name: swift-patterns description: "Swift/iOS: SwiftUI, Combine, async/await, actors, SPM, Core Data, UIKit interop. Triggers: Swift, SwiftUI, Combine, iOS, Xcode, actor, Core Data, @MainActor, @State." effort: medium user-invocable: false allowed-tools: Read
Swift / iOS Patterns
Project Structure
Swift Package (SPM)
MyPackage/ ├── Package.swift ├── Sources/ │ ├── MyLibrary/ │ └── MyExecutable/ ├── Tests/ │ └── MyLibraryTests/ └── Plugins/
// swift-tools-version: 5.10
import PackageDescription
let package = Package(
name: "MyPackage",
platforms: [.iOS(.v17), .macOS(.v14)],
products: [
.library(name: "MyLibrary", targets: ["MyLibrary"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-algorithms", from: "1.2.0"),
],
targets: [
.target(name: "MyLibrary",
dependencies: [.product(name: "Algorithms", package: "swift-algorithms")]),
.testTarget(name: "MyLibraryTests", dependencies: ["MyLibrary"]),
]
)Xcode Project Layout
MyApp/ ├── MyApp/ │ ├── App/ # Entry point, ContentView │ ├── Features/ # Feature modules (Views, ViewModels, Models) │ ├── Core/ # Networking, Storage, Extensions │ └── Resources/ # Assets.xcassets, Info.plist ├── MyAppTests/ └── MyAppUITests/
---
Idioms / Code Style
Optionals
// guard-let for early exit
func process(user: User?) {
guard let user else { return }
print(user.name)
}
// Optional chaining + nil coalescing
let name = user?.profile?.displayName ?? "Anonymous"
// map/flatMap on optionals
let length: Int? = optionalString.map { $0.count }
// Never force-unwrap in production: user!.nameProtocol-Oriented Programming
protocol Cacheable: Identifiable where ID: Hashable {
var cacheKey: String { get }
}
extension Cacheable where ID == String {
var cacheKey: String { id }
}Value Types vs Reference Types
Use structs by default (value semantics, thread-safe). Use classes when identity matters, shared mutable state is intentional, inheritance is needed, or ObjC interop is required.
Property Wrappers
@propertyWrapper
struct Clamped<Value: Comparable> {
var wrappedValue: Value {
didSet { wrappedValue = min(max(wrappedValue, range.lowerBound), range.upperBound) }
}
let range: ClosedRange<Value>
init(wrappedValue: Value, _ range: ClosedRange<Value>) {
self.range = range
self.wrappedValue = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
}
struct Volume {
@Clamped(0...100) var level: Int = 50
}Result Builders
@resultBuilder
struct ArrayBuilder<Element> {
static func buildBlock(_ components: [Element]...) -> [Element] { components.flatMap { $0 } }
static func buildExpression(_ expression: Element) -> [Element] { [expression] }
static func buildOptional(_ component: [Element]?) -> [Element] { component ?? [] }
}---
Error Handling
throws / try / catch
enum NetworkError: Error, LocalizedError {
case invalidURL
case timeout(seconds: Int)
case serverError(statusCode: Int)
var errorDescription: String? {
switch self {
case .invalidURL: "Invalid URL."
case .timeout(let s): "Timed out after \(s)s."
case .serverError(let code): "Server returned \(code)."
}
}
}
do {
let user = try fetchUser(id: "123")
} catch let error as NetworkError {
handleNetworkError(error)
} catch {
handleUnexpected(error)
}
let user = try? fetchUser(id: "123") // nil on errorResult Type
switch fetchData(from: url) {
case .success(let data): process(data)
case .failure(let error): showError(error)
}Typed Throws (Swift 6) and Async Throws
func load() throws(DatabaseError) -> [Item] { /* compiler-enforced error type */ }
func fetchUser(id: String) async throws -> User {
let (data, response) = try await URLSession.shared.data(from: url)
guard let http = response as? HTTPURLResponse, http.statusCode == 200 else {
throw NetworkError.serverError(statusCode: 0)
}
return try JSONDecoder().decode(User.self, from: data)
}---
Testing
XCTest
final class UserServiceTests: XCTestCase {
var sut: UserService!
var mockRepo: MockUserRepository!
override func setUp() {
mockRepo = MockUserRepository()
sut = UserService(repository: mockRepo)
}
func testFetchUser_success() async throws {
mockRepo.stubbedUser = User(id: "1", name: "Alice")
let user = try await sut.fetchUser(id: "1")
XCTAssertEqual(user.name, "Alice")
}
func testFetchUser_notFound_throws() async {
mockRepo.shouldFail = true
do {
_ = try await sut.fetchUser(id: "999")
XCTFail("Expected error")
} catch {
XCTAssertTrue(error is UserService.Error)
}
}
}Swift Testing Framework (Swift 6+)
import Testing
@Suite("UserService")
struct UserServiceTests {
@Test("fetches user by ID")
func fetchUser() async throws {
let mockRepo = MockUserRepository()
mockRepo.stubbedUser = User(id: "1", name: "Alice")
let sut = UserService(repository: mockRepo)
let user = try await sut.fetchUser(id: "1")
#expect(user.name == "Alice")
}
@Test("throws on missing user", arguments: ["999", ""])
func fetchMissingUser(id: String) async {
let sut = UserService(repository: MockUserRepository())
await #expect(throws: UserService.Error.self) {
try await sut.fetchUser(id: id)
}
}
}Protocol-Based Mocking
protocol UserRepository {
func fetch(id: String) async throws -> User
}
final class MockUserRepository: UserRepository {
var stubbedUser: User?
var shouldFailProfessional-grade AI coding toolkit with multi-platform support. Machine-enforced safety, 109 skills, 44 agents, expanded lifecycle hooks, persona presets, experimental opt-in plugin packs, and benchmark tooling — works with Claude Code, Claude Chat/Cowork,
Repo: softspark/ai-toolkit
Other skills on ai-toolkit.
- /ai-toolkit-rules
Mandatory engineering, security, testing, git, performance, quality, and response rules. Claude MUST load this skill for every technical, coding, debugging, review, architecture, DevOps, data, or file-editing task in Chat or Cowork.
Open skill - /mem-search
Search past coding sessions using natural language. Finds relevant observations, decisions, and context from previous work.
Open skill - /a11y-validate
Accessibility validator: WCAG 2.1 AA, EN 301 549, EAA. Triggers: a11y, accessibility, WCAG, EAA, ARIA, contrast, keyboard, screen reader.
Open skill - /agent-creator
Creates new specialized agents with frontmatter, tools, delegation. Triggers: new agent, create agent, agent scaffold, specialized agent.
Open skill - /analyze
Analyzes code quality, complexity, patterns across codebase. Triggers: quality report, hotspot scan, code analysis, architecture signal.
Open skill - /api-patterns
REST/GraphQL API design: naming, versioning, pagination, idempotency, OpenAPI. Triggers: API design, REST, GraphQL, OpenAPI, Swagger, idempotency, rate limit.
Open skill

