swift-guide

Applies to: Swift 5.9+, iOS/macOS/Server-Side, SPM, Concurrency

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "swift-guide" with this command: npx skills add ar4mirez/samuel/ar4mirez-samuel-swift-guide

Swift Guide

Applies to: Swift 5.9+, iOS/macOS/Server-Side, SPM, Concurrency

Core Principles

  • Value Semantics First: Prefer structs over classes; use classes only when identity or inheritance is required

  • Protocol-Oriented Design: Compose behavior through protocols and extensions rather than class hierarchies

  • Safe Optionals: Unwrap explicitly with guard let or if let ; force-unwrap (! ) is forbidden outside tests

  • Structured Concurrency: Use async/await and task groups; avoid raw GCD queues for new code

  • Compiler as Ally: Enable strict concurrency checking; treat warnings as errors in CI

Guardrails

Version & Dependencies

  • Use Swift 5.9+ with Swift Package Manager (Package.swift)

  • Pin package versions with .upToNextMinor(from:) for libraries

  • Run swift package resolve before committing after dependency changes

  • Commit Package.resolved for applications; omit for libraries

Code Style

  • Run swift format or SwiftLint before every commit

  • Follow Swift API Design Guidelines

  • PascalCase for types, protocols, enums | camelCase for functions, properties, variables

  • Prefer trailing closure syntax for the last closure parameter

  • Use Self instead of repeating the type name inside type definitions

  • Mark classes final by default; remove final only when subclassing is designed for

Optionals

  • Use guard let for early exit when the unwrapped value is needed afterward

  • Use if let for scoped unwrapping within a branch

  • Use nil coalescing (?? ) for default values; optional chaining (?. ) to traverse

  • Never force-unwrap (! ) outside of tests and IBOutlet declarations

  • Use compactMap to filter nil from collections; Optional.map /.flatMap to transform

Concurrency

  • Use async/await for all asynchronous operations (no completion handlers in new code)

  • Use TaskGroup / ThrowingTaskGroup for parallel fan-out

  • Mark UI-bound code with @MainActor ; avoid DispatchQueue.main in new code

  • Use actor for mutable shared state; prefer actors over locks

  • Conform types crossing isolation boundaries to Sendable

  • Enable strict concurrency: -strict-concurrency=complete

  • Always handle Task.isCancelled or Task.checkCancellation() in long-running work

Protocols

  • Define protocols where they are consumed, not where they are implemented

  • Keep protocols focused: prefer multiple small protocols over one large one

  • Use protocol extensions for default implementations of computed logic

  • Use protocol composition (SomeProtocol & AnotherProtocol ) for flexible constraints

  • Prefer some Protocol (opaque types) over any Protocol when the concrete type is fixed

Project Structure

MyProject/ ├── Package.swift # Manifest (targets, dependencies, platforms) ├── Package.resolved # Locked versions (commit for apps) ├── Sources/ │ ├── MyProject/ # Main library target │ │ ├── Models/ │ │ ├── Services/ │ │ ├── Protocols/ │ │ └── Extensions/ # TypeName+Capability.swift │ └── MyProjectCLI/ # Executable target (thin entry point) │ └── main.swift ├── Tests/ │ └── MyProjectTests/ └── README.md

  • main.swift or @main struct should be thin: parse arguments, build dependencies, call library code

  • Put all business logic in library targets (testable without running the binary)

  • One primary type per file, file named after the type

Key Patterns

Optionals: guard let / if let

func processUser(id: String?) -> User { guard let id, !id.isEmpty else { return User.anonymous } guard let user = userCache[id] else { return fetchUser(id: id) } return user }

func displayName(for user: User) -> String { if let nickname = user.nickname { return nickname } return "(user.firstName) (user.lastName)" }

Result Type for Typed Errors

enum NetworkError: Error, Sendable { case invalidURL(String) case serverError(statusCode: Int) case decodingFailed(underlying: Error) }

func fetchData(from urlString: String) async -> Result<Data, NetworkError> { guard let url = URL(string: urlString) else { return .failure(.invalidURL(urlString)) } do { let (data, response) = try await URLSession.shared.data(from: url) guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else { return .failure(.serverError(statusCode: 0)) } return .success(data) } catch { return .failure(.decodingFailed(underlying: error)) } }

Protocol Extensions with Defaults

protocol Timestamped { var createdAt: Date { get } var updatedAt: Date { get } }

extension Timestamped { var isRecent: Bool { updatedAt.timeIntervalSinceNow > -86_400 } }

// Protocol composition for flexible constraints func findRecent<T: Identifiable & Timestamped>(_ items: [T]) -> [T] { items.filter(.isRecent) }

Actor for Shared Mutable State

actor CacheStore<Key: Hashable & Sendable, Value: Sendable> { private var storage: [Key: Value] = [:] private let maxSize: Int

init(maxSize: Int = 1000) { self.maxSize = maxSize }

func get(_ key: Key) -> Value? { storage[key] }

func set(_ key: Key, value: Value) {
    if storage.count >= maxSize { storage.removeAll() }
    storage[key] = value
}

}

Async/Await with Task Groups

func fetchAllUsers(ids: [String]) async throws -> [User] { try await withThrowingTaskGroup(of: User.self) { group in for id in ids { group.addTask { try await self.fetchUser(id: id) } } var users: [User] = [] for try await user in group { users.append(user) } return users } }

Sendable Conformance

// Value types: implicitly Sendable when all stored properties are Sendable struct UserDTO: Sendable { let id: String; let name: String }

// Classes: must be final with immutable properties, or use @unchecked with a lock final class AppConfig: Sendable { let apiBaseURL: URL; let maxRetries: Int init(apiBaseURL: URL, maxRetries: Int = 3) { self.apiBaseURL = apiBaseURL; self.maxRetries = maxRetries } }

Property Wrappers

@propertyWrapper struct Clamped<Value: Comparable> { private var value: Value private let range: ClosedRange<Value>

var wrappedValue: Value {
    get { value }
    set { value = min(max(newValue, range.lowerBound), range.upperBound) }
}

init(wrappedValue: Value, _ range: ClosedRange&#x3C;Value>) {
    self.range = range
    self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
}

}

struct AudioSettings { @Clamped(0...100) var volume: Int = 50 @Clamped(0.5...2.0) var playbackSpeed: Double = 1.0 }

Result Builders for DSLs

@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 ?? [] } static func buildEither(first c: [Element]) -> [Element] { c } static func buildEither(second c: [Element]) -> [Element] { c } }

Testing

XCTest with Async Support

import XCTest @testable import MyProject

final class UserServiceTests: XCTestCase { private var sut: UserService! private var mockRepo: MockUserRepository!

override func setUp() { super.setUp(); mockRepo = MockUserRepository(); sut = UserService(repository: mockRepo) }
override func tearDown() { sut = nil; mockRepo = nil; super.tearDown() }

func test_fetchUser_withValidID_returnsUser() async throws {
    mockRepo.stubbedUser = User(id: "1", name: "Alice")
    let user = try await sut.fetchUser(id: "1")
    XCTAssertEqual(user.name, "Alice")
}

func test_fetchUser_withInvalidID_throwsNotFound() async {
    mockRepo.stubbedError = .notFound
    do {
        _ = try await sut.fetchUser(id: "invalid")
        XCTFail("Expected notFound error")
    } catch let error as ServiceError {
        XCTAssertEqual(error, .notFound)
    } catch {
        XCTFail("Unexpected error: \(error)")
    }
}

}

Testing Standards

  • Test names describe behavior: func test_login_withExpiredToken_refreshesAutomatically()

  • Use setUp() / tearDown() for shared test fixtures

  • Use protocol-based mocks injected via initializer (no singletons)

  • Async tests use async throws directly (no XCTestExpectation for async/await code)

  • Coverage target: >80% for business logic, >60% overall

  • Test both success and failure paths for every public method

Tooling

Essential Commands

swift build # Build all targets swift test # Run all tests swift test --enable-code-coverage # With coverage swift package resolve # Resolve dependencies swift package update # Update dependencies swift format . # Format (swift-format) swiftlint # Lint (SwiftLint) swiftlint --fix # Auto-fix lint issues

SwiftLint Key Rules

.swiftlint.yml -- enforce these as errors

force_cast: error force_unwrapping: error force_try: error function_body_length: warning: 40 error: 50 cyclomatic_complexity: warning: 8 error: 10

Package.swift Essentials

// swift-tools-version: 5.9 import PackageDescription

let package = Package( name: "MyProject", platforms: [.macOS(.v14), .iOS(.v17)], dependencies: [ .package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0"), .package(url: "https://github.com/apple/swift-log", from: "1.5.0"), ], targets: [ .target(name: "MyProject", dependencies: [ .product(name: "Logging", package: "swift-log"), ], swiftSettings: [ .enableExperimentalFeature("StrictConcurrency"), ]), .testTarget(name: "MyProjectTests", dependencies: ["MyProject"]), ] )

References

For detailed patterns and examples, see:

  • references/patterns.md -- Actor patterns, protocol composition, async sequences

External References

  • The Swift Programming Language

  • Swift API Design Guidelines

  • Swift Evolution Proposals

  • Swift Package Manager Docs

  • SwiftLint

  • swift-format

  • Swift on Server

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

actix-web

No summary provided by upstream source.

Repository SourceNeeds Review
General

frontend-design

No summary provided by upstream source.

Repository SourceNeeds Review
General

blazor

No summary provided by upstream source.

Repository SourceNeeds Review
General

go-guide

No summary provided by upstream source.

Repository SourceNeeds Review