Swift Patterns
Value Types First
Prefer structs and enums over classes unless you need reference semantics.
// Good — value type, copyable, predictable
struct Point {
var x: Double
var y: Double
func distance(to other: Point) -> Double {
sqrt(pow(x - other.x, 2) + pow(y - other.y, 2))
}
}
// Only use class when you need shared mutable state or ObjC interop
final class UserSession {
private(set) var token: String
init(token: String) { self.token = token }
}
Enums with Associated Values
Model domain states exhaustively — never use stringly-typed sentinels.
enum AuthState {
case unauthenticated
case authenticating
case authenticated(User)
case failed(AuthError)
}
func handle(_ state: AuthState) {
switch state {
case .unauthenticated: showLogin()
case .authenticating: showSpinner()
case .authenticated(let u): showDashboard(user: u)
case .failed(let e): showError(e)
}
}
Optionals
// Use guard for early exit
func process(input: String?) -> String {
guard let value = input, !value.isEmpty else { return "" }
return value.uppercased()
}
// map/flatMap on Optional avoids repeated unwrapping
let length = name?.count
let trimmed = name?.trimmingCharacters(in: .whitespaces)
// Force unwrap only for known-safe static values
let url = URL(string: "https://api.example.com")!
Result Type
enum NetworkError: Error {
case invalidURL, timeout, serverError(Int), decodingFailed
}
func fetch<T: Decodable>(url: URL) async -> Result<T, NetworkError> {
do {
let (data, response) = try await URLSession.shared.data(from: url)
guard let http = response as? HTTPURLResponse, http.statusCode == 200 else {
return .failure(.serverError((response as? HTTPURLResponse)?.statusCode ?? 0))
}
return .success(try JSONDecoder().decode(T.self, from: data))
} catch is DecodingError {
return .failure(.decodingFailed)
} catch {
return .failure(.timeout)
}
}
Property Wrappers
@propertyWrapper
struct Clamped<T: Comparable> {
private var value: T
let range: ClosedRange<T>
init(wrappedValue: T, _ range: ClosedRange<T>) {
self.range = range
self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
var wrappedValue: T {
get { value }
set { value = min(max(newValue, range.lowerBound), range.upperBound) }
}
}
struct Player {
@Clamped(0...100) var health: Int = 100
@Clamped(0...1) var speed: Double = 0.5
}
Pattern Matching
// if case let for single-case matching
if case .authenticated(let user) = authState {
print("Logged in as \(user.name)")
}
// for-case in collections
let errors = results.compactMap {
if case .failure(let e) = $0 { return e } else { return nil }
}
// switch with where clauses
switch response.statusCode {
case 200..<300: handleSuccess(data)
case 400 where debug: log("Bad request: \(response)")
case 400..<500: handleClientError(response.statusCode)
case 500...: handleServerError(response.statusCode)
default: break
}
Common Anti-Patterns
- Force unwrap
!in production — useguard letorif let - Classes for pure data — structs are thread-safe by default
- Stringly-typed state — model with enums and associated values
- Ignoring
Result— propagate errors; avoid sentinelnilreturns @objceverywhere — annotate only what ObjC actually needs