Why Swift Needs Specific AI Coding Rules
Swift is designed around protocols, value types, and functional patterns — but AI assistants trained primarily on Java, Python, and JavaScript default to class-heavy, reference-type-first code. The result is Swift that compiles but fights the language's design philosophy.
The most common AI failures in Swift: using classes where structs would be appropriate, creating deep class hierarchies instead of protocol compositions, ignoring Swift's powerful enum associated values, generating UIKit patterns in SwiftUI projects, and using completion handlers instead of async/await in modern Swift.
Apple's Swift API Design Guidelines are comprehensive but AI assistants don't follow them consistently. Your CLAUDE.md bridges that gap — it tells the AI to write Swift the way Apple intended, not the way Java developers would write it in Swift syntax.
Rule 1: Prefer Value Types and Protocol-Oriented Design
The rule: 'Use structs as the default type for data. Only use classes when you need reference semantics, inheritance, or Objective-C interop. Use protocols to define behavior contracts — prefer protocol composition (Protocol1 & Protocol2) over class inheritance. Use protocol extensions for default implementations instead of base classes.'
For enums: 'Use enums with associated values for modeling state machines, results, and variants. Prefer enum over boolean flags: enum LoadingState { case idle, loading, loaded(Data), failed(Error) } is better than isLoading: Bool, hasError: Bool. Make invalid states unrepresentable at the type level.'
This is the single most important Swift rule. AI assistants write 'Java in Swift' — class hierarchies with inheritance chains. Idiomatic Swift uses structs, protocols, and enums as the primary building blocks.
Use structs as the default. Only reach for classes when you need reference semantics, inheritance, or Objective-C interop. This one rule prevents the 'Java in Swift' anti-pattern.
Rule 2: SwiftUI Patterns
AI assistants frequently mix UIKit and SwiftUI patterns, generate massive view bodies instead of extracting subviews, and misuse state management — putting @State on data that should be in an ObservableObject, or using @ObservedObject where @StateObject is needed.
The rule: 'This is a SwiftUI project — never generate UIKit code. Keep view bodies under 30 lines — extract subviews as separate structs. Use @State for view-local state, @StateObject for owned observable objects, @ObservedObject for injected observable objects, @EnvironmentObject for app-wide shared state. Use the new @Observable macro (iOS 17+) instead of ObservableObject protocol where supported.'
For navigation: 'Use NavigationStack with NavigationPath for programmatic navigation. Use .navigationDestination(for:) for type-safe routing. Never use NavigationLink with destination view directly in the body — use value-based navigation.'
- SwiftUI only — never UIKit (unless wrapping with UIViewRepresentable)
- View bodies under 30 lines — extract subviews as separate structs
- @State for local, @StateObject for owned, @ObservedObject for injected
- @Observable macro (iOS 17+) over ObservableObject protocol
- NavigationStack + NavigationPath for programmatic navigation
- Prefer .task {} modifier over .onAppear for async work
Rule 3: Modern Concurrency with async/await
Swift's structured concurrency (async/await, actors, task groups) replaced completion handlers and GCD in modern Swift. But AI assistants still generate completion-handler-based code because that's what dominates their training data from years of pre-Swift 5.5 code.
The rule: 'Use async/await for all asynchronous operations. Never use completion handlers for new code. Use Task { } for launching async work from synchronous contexts. Use TaskGroup for parallel operations. Use actors for thread-safe mutable state — never use DispatchQueue.sync for thread safety. Use MainActor for UI updates.'
For Combine: 'Use Combine only for reactive streams (user input debouncing, real-time data). For simple async operations (network requests, database queries), use async/await instead of Combine publishers. Never mix completion handlers and Combine for the same operation.'
AI defaults to completion-handler patterns from pre-Swift 5.5 training data. For new code: async/await only. Use actors instead of DispatchQueue for thread safety.
Rule 4: Error Handling and Optionals
Swift's optional system and throwing functions provide two distinct error handling paths. AI assistants frequently confuse them — using optionals where throwing would be more informative, or force-unwrapping optionals instead of handling the nil case properly.
The rule: 'Use throwing functions (throws) for operations that can fail with a reason — network requests, parsing, file I/O. Use optionals for values that are legitimately absent without implying failure. Never force-unwrap (!) in production code — use guard let, if let, or the nil-coalescing operator (??). Define custom error types conforming to LocalizedError for user-facing errors.'
For Result type: 'Prefer async/await with throws over Result<Success, Failure> for new code. Use Result only at API boundaries where you need to store or pass around the outcome of an operation (e.g., in a callback-based API you can't convert to async).'
Never use ! in production Swift. Every force-unwrap is a potential crash. Use guard let for early exits, if let for conditional binding, ?? for defaults.
Rule 5: Apple API Design Guidelines
Apple's Swift API Design Guidelines cover naming, clarity, and conventions. AI assistants don't follow them consistently, generating method names that read like Java (getUserName()) instead of Swift (name(for:)).
The rule: 'Follow Apple's Swift API Design Guidelines. Name methods as verb phrases for mutating operations (sort(), append()), noun phrases for non-mutating (sorted(), appending()). Use argument labels that read as English phrases: move(from: start, to: end). Omit needless words: removeElement() should be remove(). Use camelCase for everything except types (PascalCase).'
For naming specifics: 'Boolean properties read as assertions: isEmpty, isValid, hasPermission. Factory methods use make prefix: makeIterator(). Type names are nouns (User, PaymentProcessor). Protocol names end in -able, -ible, or -ing for capabilities (Equatable, Codable, Loading).'
Complete Swift Rules Template
Consolidated template for Swift/SwiftUI teams targeting iOS 17+.
- Structs over classes by default — classes only for reference semantics or ObjC interop
- Protocol-oriented design — protocol composition over inheritance hierarchies
- Enums with associated values for state — make invalid states unrepresentable
- SwiftUI only — view bodies under 30 lines, correct property wrappers
- async/await for all async — no completion handlers, actors for thread safety
- No force-unwrap (!) — guard let, if let, or ?? for optionals
- Apple API Design Guidelines — verb phrases for mutating, noun phrases for non-mutating
- Swift Testing framework (iOS 18+) or XCTest — #expect macro over XCTAssert