LLMSkillHub
mirroredCopied from irahardianto/awesome-agv under MIT. irahardianto has not claimed this page, and is not affiliated with LLM SkillHub.

irahardianto / swift-idioms

Swift idioms and best practices: value types, Optionals hygiene, protocol-oriented design, async/await and Actor concurrency, and XCTest. Use when writing, refactoring, or reviewing Swift iOS, macOS, or server-side code.

0
SKILL.md
8.3 KB

Swift Idioms and Patterns

Swift rewards value types, optionals, and protocol-oriented design. Idiomatic Swift = safe, expressive, Swifty.

Scope: Swift coding idioms. Test naming: .agents/rules/testing-strategy.md.

Value Types and Optionals

  1. Prefer structs over classes — value semantics by default. Classes only for identity, inheritance, or reference counting.
  2. Optionals — never force-unwrap (!) in production:
    `swift
    // ✅ Guard let for early exit
    guard let task = storage.findById(id) else {
    throw TaskError.notFound(id)
    }

    // ✅ Optional chaining
    let title = task?.title ?? "Untitled"

    // ✅ if let for conditional binding
    if let deadline = task.deadline {
    scheduleReminder(for: deadline)
    }

    // ❌ Force unwrap — crash risk
    let task = storage.findById(id)!
    `

  3. Property wrappers for reusable behavior:
    `swift
    @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 Task {
    @Clamped(0...100) var progress: Int = 0
    }
    `

Error Handling

For universal error handling principles, see .agents/rules/error-handling-principles.md.

  1. Typed throws (Swift 6) or Error protocol:
    `swift
    enum TaskError: Error, LocalizedError {
    case notFound(String)
    case validationFailed(field: String, message: String)
    case storageUnavailable

    var errorDescription: String? {
    switch self {
    case .notFound(let id): "Task '(id)' not found"
    case .validationFailed(let field, let msg): "Validation failed on (field): (msg)"
    case .storageUnavailable: "Storage is unavailable"
    }
    }
    }

    func getTask(id: String) throws(TaskError) -> Task { ... }
    `

  2. Result type for async callbacks (pre-async/await):
    `swift
    func fetchTask(id: String) async -> Result<Task, TaskError> { ... }
    `

  3. do/catch with pattern matching:
    `swift
    do {
    let task = try getTask(id: "123")
    process(task)
    } catch TaskError.notFound(let id) {
    logger.warn("Task not found", metadata: ["taskId": id])
    } catch {
    logger.error("Unexpected error", metadata: ["error": "(error)"])
    }
    `

  4. defer for cleanup:
    `swift
    func processFile(at path: String) throws -> Data {
    let handle = try FileHandle(forReadingFrom: URL(fileURLWithPath: path))
    defer { handle.closeFile() } // ✅ Always runs on exit

    return handle.readDataToEndOfFile()
    }
    `

Protocol-Oriented Design

  1. Protocols over abstract classes:
    `swift
    // ✅ Interface defined as protocol
    protocol TaskStorage {
    func getById(_ id: String) async throws -> Task?
    func save(_ task: Task) async throws
    }

    // ✅ Production implementation
    struct PostgresTaskStorage: TaskStorage {
    let pool: ConnectionPool

    func getById(_ id: String) async throws -> Task? {
    try await pool.query("SELECT * FROM tasks WHERE id = $1", [id]).first
    }

    func save(_ task: Task) async throws {
    try await pool.execute("INSERT INTO tasks ...", [task.id, task.title])
    }
    }

    // ✅ Test implementation
    struct MockTaskStorage: TaskStorage {
    var tasks: [Task] = []

    func getById(_ id: String) async throws -> Task? {
    tasks.first { $0.id == id }
    }

    func save(_ task: Task) async throws {
    tasks.append(task)
    }
    }
    `

  2. Protocol extensions for default implementations:
    `swift
    protocol Identifiable {
    var id: String { get }
    }

    extension Identifiable {
    var isNew: Bool { id.isEmpty }
    }
    `

  3. Associated types for generic protocols:
    `swift
    protocol Repository {
    associatedtype Entity
    func findById(_ id: String) async throws -> Entity?
    func save(_ entity: Entity) async throws
    }
    `

Concurrency

  1. Structured concurrency with async/await:
    `swift
    func loadDashboard() async throws -> Dashboard {
    async let user = fetchUser(id)
    async let tasks = fetchTasks(userId: id)
    async let stats = fetchStats()

    return Dashboard(
    user: try await user,
    tasks: try await tasks,
    stats: try await stats
    )
    }
    `

  2. @Sendable for closures crossing concurrency domains.
  3. Actors for thread-safe mutable state:
    `swift
    actor TaskCache {
    private var cache: [String: Task] = [:]

    func get(_ id: String) -> Task? { cache[id] }
    func set(_ id: String, task: Task) { cache[id] = task }
    func invalidate(_ id: String) { cache.removeValue(forKey: id) }
    }

    // ✅ Safe concurrent access
    let cache = TaskCache()
    await cache.set("123", task: newTask)
    if let task = await cache.get("123") { ... }
    `

  4. TaskGroup for dynamic concurrency:
    `swift
    func fetchAllTasks(ids: [String]) async throws -> [Task] {
    try await withThrowingTaskGroup(of: Task.self) { group in
    for id in ids {
    group.addTask { try await fetchTask(id: id) }
    }
    return try await group.reduce(into: []) { $0.append($1) }
    }
    }
    `

Naming (Swift API Design Guidelines)

  1. camelCase for functions, properties, variables.
  2. PascalCase for types, protocols, enums.
  3. Omit needless words — remove(at:) not removeItem(atIndex:).
  4. Protocols for capabilities use -able/-ible: Codable, Identifiable.
  5. Factory methods use make prefix: makeIterator().
  6. Boolean properties read as assertions: isEmpty, hasChanges, isValid.

Anti-Patterns

  • ❌ Force unwrap (!) in production code — crashes at runtime
  • ❌ var when let suffices — always prefer immutability
  • ❌ Classes when structs work — unnecessary reference semantics
  • ❌ Stringly-typed APIs — use enums for finite option sets
  • ❌ Massive view controllers — extract to view models, coordinators
  • ❌ try? silently discarding errors — log or handle the error case
  • ❌ Nested if let pyramids — use guard let for early returns
// ❌ Pyramid of doom
if let user = getUser() {
    if let tasks = getTasks(for: user) {
        if let first = tasks.first {
            process(first)
        }
    }
}

// ✅ Flat with guard
guard let user = getUser() else { return }
guard let tasks = getTasks(for: user), let first = tasks.first else { return }
process(first)

Testing

XCTest or Swift Testing (6.0+). Mock via protocols.

// XCTest
final class TaskServiceTests: XCTestCase {
    func testGetTask_returnsNotFound() async throws {
        let storage = MockTaskStorage()
        let service = TaskService(storage: storage)

        do {
            _ = try await service.getTask(id: "999")
            XCTFail("Expected notFound error")
        } catch TaskError.notFound(let id) {
            XCTAssertEqual(id, "999")
        }
    }
}

// Swift Testing (6.0+)
@Test func getTask_returnsNotFound() async throws {
    let storage = MockTaskStorage()
    let service = TaskService(storage: storage)

    #expect(throws: TaskError.notFound("999")) {
        try await service.getTask(id: "999")
    }
}

Formatting and Static Analysis

ToolPurposeCommand
swift-formatFormattingswift-format -i -r Sources/
SwiftLintLintingswiftlint lint --strict
Xcode AnalyzerStatic analysisBuilt-in

Related

  • Code Idioms and Conventions .agents/rules/code-idioms-and-conventions.md
  • Testing Strategy .agents/rules/testing-strategy.md
  • Error Handling Principles .agents/rules/error-handling-principles.md
  • Concurrency and Threading Principles @.agents/rules/concurrency-and-threading-principles.md