--- id: irahardianto/awesome-agv/swift-idioms version: "c119f494" license: MIT install: manual updated: 2026-07-17 --- # Swift Idioms — This skill covers the core patterns that make Swift code safe and maintainable. You'll learn when to use structs over classes, how to handle optionals without crashes, design with protocols instead of inheritance, and leverage Swift's concurrency model. The guide includes practical examples of error handling, property wrappers, actors, and testing strategies—plus anti-patterns to avoid. Publisher: irahardianto · Stars: 150 · Updated: 2026-07-17 Install (manual): `git clone https://github.com/irahardianto/awesome-agv` ## SKILL.md ## 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 { var wrappedValue: Value { didSet { wrappedValue = min(max(wrappedValue, range.lowerBound), range.upperBound) } } let range: ClosedRange init(wrappedValue: Value, _ range: ClosedRange) { 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 { ... } ``` 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 ```swift // ❌ 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. ```swift // 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 | Tool | Purpose | Command | |---|---|---| | `swift-format` | Formatting | `swift-format -i -r Sources/` | | SwiftLint | Linting | `swiftlint lint --strict` | | Xcode Analyzer | Static analysis | Built-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 [View on SkillFed](https://skillfed.io/irahardianto/awesome-agv/swift-idioms) · [View on GitHub](https://github.com/irahardianto/awesome-agv)