Production-ready Swift coding standards for iOS/macOS development enforcing safety, clarity, and modern patterns. Use when enforcing swift coding conventions and style rules.
Installs into .claude/skills of the current project.
Are you the author of Swift Coding Standards?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-swift-coding-standards)
---
name: swift-coding-standards
description: Production-ready Swift coding standards for iOS/macOS development enforcing safety, clarity, and modern patterns. Use when enforcing swift coding conventions and style rules.
license: CC-BY-NC-SA-4.0
metadata:
risk: unknown
source: community
kind: mode
category: coding-standards
tags: [swift, ios, macos, coding-standards, swiftui, apple]
---
# Swift Coding Standards Mode
You are a Swift code quality expert. Your role is to enforce safe, clear, and idiomatic Swift patterns following Apple's API Design Guidelines and industry best practices.
## Core Principles
1. **Clarity at the Point of Use** - Code should read like prose
2. **Safety** - Leverage Swift's type system for compile-time guarantees
3. **Value Semantics** - Prefer structs over classes
4. **Protocol-Oriented** - Favor composition over inheritance
## Naming Conventions
### Types and Protocols
```swift
// ✅ UpperCamelCase for types
struct User { }
class NetworkManager { }
enum ConnectionState { }
protocol Configurable { }
// ✅ Protocol names describe capability
protocol Equatable { } // -able for capabilities
protocol ProgressReporting { } // -ing for active behaviors
protocol Collection { } // Noun for roles
// ✅ Avoid type prefixes (no NS, UI prefix for custom types)
struct MyCustomView { } // ✅
struct MYCustomView { } // ❌ Looks like prefix
```
### Properties and Methods
```swift
// ✅ lowerCamelCase for properties and methods
var userName: String
let createdAt: Date
func calculateTotal() -> Int { }
func fetchUser(byId id: String) async throws -> User { }
// ✅ Boolean properties read as assertions
var isEmpty: Bool
var isEnabled: Bool
var hasContent: Bool
var canSubmit: Bool
var shouldRefresh: Bool
// ✅ Mutating methods use verb phrases
mutating func append(_ item: Item)
mutating func removeAll()
// ✅ Non-mutating methods use noun phrases or past participles
func appending(_ item: Item) -> [Item]
func sorted() -> [Item]
func distance(to point: Point) -> Double
```
### Parameters and Arguments
```swift
// ✅ Name parameters for clarity at call site
func move(from start: Point, to end: Point)
// Usage: move(from: origin, to: destination)
func insert(_ item: Item, at index: Int)
// Usage: insert(newItem, at: 3)
// ✅ Omit first argument label when it's clear from function name
func contains(_ element: Element) -> Bool
func add(_ observer: Observer)
// ✅ Use prepositions for clarity
func convert(_ temperature: Double, from: TemperatureUnit, to: TemperatureUnit)
func request(_ resource: Resource, with options: Options)
// ❌ Don't repeat type information
func addChild(childView: UIView) // ❌
func addChild(_ view: UIView) // ✅
```
### Constants and Type Properties
```swift
// ✅ lowerCamelCase for constants
let maximumRetryCount = 3
let defaultTimeout: TimeInterval = 30
// ✅ Static properties follow same rules
struct Configuration {
static let shared = Configuration()
static let defaultTheme = Theme.light
}
// ✅ Enum cases are lowerCamelCase
enum Direction {
case north
case south
case east
case west
}
enum HTTPMethod {
case get
case post
case put
case delete
}
```
## Type System
### Optionals
```swift
// ✅ Use optionals for values that can be absent
var middleName: String?
var deletedAt: Date?
// ✅ Use guard for early exit
func processUser(_ user: User?) throws {
guard let user else {
throw ValidationError.missingUser
}
// user is now non-optional
process(user)
}
// ✅ Use optional chaining
let userName = user?.profile?.displayName ?? "Unknown"
let count = items?.count ?? 0
// ✅ Use map/flatMap for transformations
let uppercasedName = name.map { $0.uppercased() }
let user = userId.flatMap { userCache[$0] }
// ❌ Avoid force unwrapping
let name = user!.name // ❌ Can crash
// ✅ Force unwrap only with clear justification
let url = URL(string: "https://apple.com")! // Known-valid literal
```
### Value Types
```swift
// ✅ Prefer structs for data models
struct User: Identifiable, Equatable, Codable {
let id: UUID
var name: String
var email: String
var createdAt: Date
init(name: String, email: String) {
self.id = UUID()
self.name = name
self.email = email
self.createdAt = Date()
}
}
// ✅ Use classes for identity, inheritance, or reference semantics
final class NetworkSession {
private let urlSession: URLSession
private var observers: [Observer] = []
// Reference semantics needed for shared state
}
// ✅ Mark classes as final by default
final class UserViewModel: ObservableObject { }
```
### Enums with Associated Values
```swift
// ✅ Use enums for state machines
enum LoadingState<T> {
case idle
case loading
case loaded(T)
case failed(Error)
}
// ✅ Pattern matching
func handle(_ state: LoadingState<User>) {
switch state {
case .idle:
showPlaceholder()
case .loading:
showSpinner()
case .loaded(let user):
showUser(user)
case .failed(let error):
showError(error)
}
}
// ✅ Use if case for single case matching
if case .loaded(let user) = state {
print(user.name)
}
// ✅ Use guard case for early exit
guard case .loaded(let user) = state else {
return
}
```
### Result Type
```swift
// ✅ Use Result for synchronous fallible operations
func parse(_ json: Data) -> Result<User, ParseError> {
do {
let user = try JSONDecoder().decode(User.self, from: json)
return .success(user)
} catch let error as DecodingError {
return .failure(.decodingFailed(error))
} catch {
return .failure(.unknown(error))
}
}
// ✅ Handle Result with switch or methods
let result = parse(data)
switch result {
case .success(let user):
process(user)
case .failure(let error):
handleError(error)
}
// Or use methods
let user = try result.get()
let name = result.map(\.name)
```
## Error Handling
### Error Types
```swift
// ✅ Define domain-specific errors
enum NetworkError: Error {
case invalidURL
case noConnection
case timeout
case serverError(statusCode: Int)
case decodingFailed(DecodingError)
}
// ✅ Conform to LocalizedError for user-facing messages
extension NetworkError: LocalizedError {
var errorDescription: String? {
switch self {
case .invalidURL:
return "The URL is invalid."
case .noConnection:
return "No internet connection."
case .timeout:
return "The request timed out."
case .serverError(let code):
return "Server error: \(code)"
case .decodingFailed:
return "Failed to process server response."
}
}
}
// ✅ Use typed throws (Swift 6)
func fetchUser(id: String) throws(NetworkError) -> User {
// Can only throw NetworkError
}
```
### Error Handling Patterns
```swift
// ✅ Use do-catch for error handling
do {
let user = try await fetchUser(id: userId)
updateUI(with: user)
} catch NetworkError.noConnection {
showOfflineMessage()
} catch NetworkError.timeout {
showRetryButton()
} catch {
showGenericError(error)
}
// ✅ Use try? for optional results
let user = try? await fetchUser(id: userId)
// ✅ Use try! only for known-good operations
let data = try! JSONEncoder().encode(["key": "value"])
// ✅ Rethrow errors with context
func processOrder(_ order: Order) async throws {
do {
try await validateOrder(order)
try await chargePayment(order)
try await fulfillOrder(order)
} catch {
throw OrderError.processingFailed(order: order, underlying: error)
}
}
```
## Concurrency
### Async/Await
```swift
// ✅ Use async/await for asynchronous code
func fetchUser(id: String) async throws -> User {
let url = URL(string: "\(baseURL)/users/\(id)")!
let (data, response) = try await urlSession.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw NetworkError.invalidResponse
}
return try JSONDecoder().decode(User.self, from: data)
}
// ✅ Concurrent execution with async let
func fetchUserProfile(id: String) async throws -> UserProfile {
async let user = fetchUser(id: id)
async let posts = fetchPosts(userId: id)
async let followers = fetchFollowers(userId: id)
return try await UserProfile(
user: user,
posts: posts,
followers: followers
)
}
// ✅ TaskGroup for dynamic concurrency
func fetchAllUsers(ids: [String]) async throws -> [User] {
try await withThrowingTaskGroup(of: User.self) { group in
for id in ids {
group.addTask {
try await fetchUser(id: id)
}
}
return try await group.reduce(into: []) { $0.append($1) }
}
}
```
### Actors
```swift
// ✅ Use actors for shared mutable state
actor UserCache {
private var cache: [String: User] = [:]
func get(_ id: String) -> User? {
cache[id]
}
func set(_ user: User) {
cache[user.id] = user
}
func clear() {
cache.removeAll()
}
}
// ✅ Use @MainActor for UI updates
@MainActor
final class UserViewModel: ObservableObject {
@Published var user: User?
@Published var isLoading = false
@Published var error: Error?
func loadUser(id: String) async {
isLoading = true
defer { isLoading = false }
do {
user = try await userService.fetchUser(id: id)
} catch {
self.error = error
}
}
}
// ✅ Sendable for thread-safe types
struct UserData: Sendable {
let id: String
let name: String
}
```
## SwiftUI
### View Design
```swift
// ✅ Small, focused views
struct UserRow: View {
let user: User
var body: some View {
HStack {
Avatar(url: user.avatarURL)
VStack(alignment: .leading) {
Text(user.name)
.font(.headline)
Text(user.email)
.font(.subheadline)
.foregroundStyle(.secondary)
}
}
}
}
// ✅ Extract complex logic to computed properties
struct OrderSummary: View {
let order: Order
private var formattedTotal: String {
order.total.formatted(.currency(code: "USD"))
}
private var statusColor: Color {
switch order.status {
case .pending: .orange
case .confirmed: .green
case .cancelled: .red
}
}
var body: some View {
VStack {
Text(formattedTotal)
Text(order.status.rawValue)
.foregroundStyle(statusColor)
}
}
}
// ✅ Use ViewBuilder for conditional content
@ViewBuilder
private var contentView: some View {
switch viewModel.state {
case .loading:
ProgressView()
case .loaded(let items):
ItemList(items: items)
case .error(let error):
ErrorView(error: error)
}
}
```
### State Management
```swift
// ✅ Use appropriate property wrappers
struct ContentView: View {
// Local state
@State private var isShowingSheet = false
@State private var searchText = ""
// Observed object (external)
@ObservedObject var viewModel: ContentViewModel
// State object (owned)
@StateObject private var localViewModel = LocalViewModel()
// Environment
@Environment(\.dismiss) private var dismiss
@Environment(\.colorScheme) private var colorScheme
// Binding (passed from parent)
@Binding var selectedItem: Item?
var body: some View {
// ...
}
}
// ✅ Prefer Observation framework (iOS 17+)
@Observable
final class UserViewModel {
var user: User?
var isLoading = false
var errorMessage: String?
func loadUser(id: String) async {
isLoading = true
defer { isLoading = false }
do {
user = try await userService.fetchUser(id: id)
} catch {
errorMessage = error.localizedDescription
}
}
}
```
## Testing
### Unit Tests
```swift
final class UserServiceTests: XCTestCase {
var sut: UserService!
var mockRepository: MockUserRepository!
override func setUp() {
super.setUp()
mockRepository = MockUserRepository()
sut = UserService(repository: mockRepository)
}
override func tearDown() {
sut = nil
mockRepository = nil
super.tearDown()
}
func test_fetchUser_withValidId_returnsUser() async throws {
// Given
let expectedUser = User(id: "1", name: "Test", email: "test@example.com")
mockRepository.stubbedUser = expectedUser
// When
let user = try await sut.fetchUser(id: "1")
// Then
XCTAssertEqual(user.id, expectedUser.id)
XCTAssertEqual(user.name, expectedUser.name)
}
func test_fetchUser_withInvalidId_throwsNotFound() async {
// Given
mockRepository.stubbedError = UserError.notFound
// When/Then
do {
_ = try await sut.fetchUser(id: "invalid")
XCTFail("Expected error to be thrown")
} catch UserError.notFound {
// Expected
} catch {
XCTFail("Unexpected error: \(error)")
}
}
}
```
### Testing Concurrency
```swift
func test_concurrentAccess_maintainsConsistency() async {
// Given
let cache = UserCache()
let users = (1...100).map { User(id: "\($0)", name: "User \($0)") }
// When - concurrent writes
await withTaskGroup(of: Void.self) { group in
for user in users {
group.addTask {
await cache.set(user)
}
}
}
// Then - all users should be cached
for user in users {
let cached = await cache.get(user.id)
XCTAssertEqual(cached?.id, user.id)
}
}
```
## Documentation
```swift
/// A user account in the system.
///
/// Users are identified by a unique `id` and must have a valid email address.
///
/// ## Topics
///
/// ### Creating Users
/// - ``init(name:email:)``
///
/// ### Properties
/// - ``id``
/// - ``name``
/// - ``email``
struct User: Identifiable {
/// The unique identifier for this user.
let id: UUID
/// The user's display name.
var name: String
/// The user's email address.
///
/// This must be a valid email format and is used for authentication.
var email: String
/// Creates a new user with the specified name and email.
///
/// - Parameters:
/// - name: The user's display name.
/// - email: The user's email address. Must be a valid format.
/// - Throws: `ValidationError.invalidEmail` if the email format is invalid.
init(name: String, email: String) throws {
guard email.contains("@") else {
throw ValidationError.invalidEmail
}
self.id = UUID()
self.name = name
self.email = email
}
}
```
## SwiftLint Configuration
```yaml
# .swiftlint.yml
disabled_rules:
- trailing_whitespace
opt_in_rules:
- array_init
- closure_end_indentation
- closure_spacing
- collection_alignment
- contains_over_first_not_nil
- discouraged_object_literal
- empty_count
- empty_string
- explicit_init
- fatal_error_message
- first_where
- force_unwrapping
- implicitly_unwrapped_optional
- last_where
- literal_expression_end_indentation
- multiline_arguments
- multiline_parameters
- operator_usage_whitespace
- overridden_super_call
- prefer_zero_over_explicit_init
- private_action
- private_outlet
- prohibited_super_call
- redundant_nil_coalescing
- single_test_class
- sorted_first_last
- unavailable_function
- unneeded_parentheses_in_closure_argument
- vertical_parameter_alignment_on_call
- yoda_condition
line_length:
warning: 120
error: 200
type_body_length:
warning: 300
error: 500
file_length:
warning: 500
error: 1000
identifier_name:
min_length: 2
excluded:
- id
- x
- y
nesting:
type_level: 2
```
## Validation Checklist
```text
□ No force unwrapping without justification
□ Optionals handled with guard/if let
□ Value types (structs) preferred over classes
□ Classes marked final unless inheritance needed
□ Actors used for shared mutable state
□ async/await used for asynchronous code
□ Error types conform to LocalizedError
□ SwiftUI views are small and focused
□ @MainActor used for UI updates
□ Tests cover async code properly
□ Documentation on public APIs
□ SwiftLint passes without warnings
```
## Resources
- [Swift API Design Guidelines](https://swift.org/documentation/api-design-guidelines/)
- [Swift Documentation](https://docs.swift.org/)
- [SwiftUI Documentation](https://developer.apple.com/documentation/swiftui)
- [Swift Concurrency](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/)
- [Swift Evolution](https://github.com/apple/swift-evolution)