swift

Idiomatic Swift patterns, XCTest testing, SwiftLint, and build commands for iOS/macOS projects. Covers value types, optionals, concurrency (async/await, actors), error handling, and Swift Package Manager. Use when working on Swift projects.

Swift Patterns — dog_stack

Idiomatic Swift conventions, testing patterns, and toolchain reference for iOS/macOS/Swift CLI projects.

Idiomatic Swift

Value types first

Prefer struct over class. Use class only when:

  • Reference semantics are required (shared mutable state)
  • Subclassing is needed
  • Interfacing with Objective-C
// ✓ Prefer
struct User { var name: String; var email: String }

// Use class only when needed
final class NetworkSession { /* reference semantics */ }

Optionals

  • Avoid force-unwrap (!) in production code
  • Use guard let / if let / ?? instead
  • @unchecked Sendable and ! are code review flags
// ✓
guard let user = getUser() else { return }

// ✗ in production
let user = getUser()!

Concurrency (Swift 5.5+)

// ✓ async/await
func fetchUser(id: UUID) async throws -> User {
    let data = try await URLSession.shared.data(from: url).0
    return try JSONDecoder().decode(User.self, from: data)
}

// ✓ actors for shared mutable state
actor UserCache {
    private var cache: [UUID: User] = [:]
    func get(_ id: UUID) -> User? { cache[id] }
    func set(_ id: UUID, _ user: User) { cache[id] = user }
}

Error handling

// ✓ typed errors
enum NetworkError: Error {
    case notFound, unauthorized, serverError(Int)
}

// ✓ Result type for async boundaries
func loadUser() async -> Result<User, NetworkError> { ... }

Testing with XCTest

import XCTest
@testable import MyApp

final class UserTests: XCTestCase {
    func testUserCreation() throws {
        let user = User(name: "Alice", email: "[email protected]")
        XCTAssertEqual(user.name, "Alice")
    }

    func testAsyncFetch() async throws {
        let user = try await UserService().fetch(id: testUUID)
        XCTAssertNotNil(user)
    }
}

Run tests:

swift test                        # all tests
swift test --filter UserTests     # specific test class
xcodebuild test -scheme MyApp -destination 'platform=iOS Simulator,name=iPhone 15'

Build Commands

swift build                        # debug build
swift build -c release             # release build
swift run MyTarget                 # run executable target
swift package resolve              # resolve dependencies
swift package update               # update dependencies

SwiftLint

swiftlint                          # lint current directory
swiftlint --fix                    # auto-fix violations
swiftlint lint --reporter json     # JSON report

# .swiftlint.yml (minimal config)
disabled_rules:
  - trailing_whitespace
opt_in_rules:
  - empty_count
  - explicit_init
line_length: 120

Common Pitfalls

PitfallFix
Force-unwrap in productionUse guard let or ??
Capture list in closuresUse [weak self] to avoid retain cycles
DispatchQueue + async/await mixedPick one concurrency model per module
Unhandled try? swallowing errorsLog or propagate errors explicitly
Large struct copied repeatedlyConsider class or @_disfavoredOverload

Related

  • Rules: rules/swift/coding-style.md
  • Agent: language reviewers (general-purpose for Swift)