| name | swift-best-practices |
| description | This skill should be used when writing or reviewing Swift code for iOS or macOS projects. Apply modern Swift 6+ best practices, concurrency patterns, API design guidelines, and migration strategies. Covers async/await, actors, MainActor, Sendable, typed throws, and Swift 6 breaking changes. |
Swift Best Practices Skill
Overview
Apply modern Swift development best practices focusing on Swift 6+ features, concurrency safety, API design principles, and code quality guidelines for iOS and macOS projects targeting macOS 15.7+.
When to Use This Skill
Use this skill when:
- Writing new Swift code for iOS or macOS applications
- Reviewing Swift code for correctness, safety, and style
- Implementing Swift concurrency features (async/await, actors, MainActor)
- Designing Swift APIs and public interfaces
- Migrating code from Swift 5 to Swift 6
- Addressing concurrency warnings, data race issues, or compiler errors related to Sendable/isolation
- Working with modern Swift language features introduced in Swift 6 and 6.2
Core Guidelines
Fundamental Principles
- Clarity at point of use is paramount - evaluate designs by examining use cases, not just declarations
- Clarity over brevity - compact code comes from the type system, not minimal characters
- Write documentation for every public declaration - if you can't describe functionality simply, the API may be poorly designed
- Name by role, not type -
var greeting = "Hello"notvar string = "Hello" - Favour elegance through simplicity - avoid over-engineering unless complexity genuinely warrants it
Swift 6 Concurrency Model
Swift 6 enables complete concurrency checking by default with region-based isolation (SE-0414). The compiler now proves code safety, eliminating many false positives whilst catching real concurrency issues at compile time.
Critical understanding:
- Async ≠ background - async functions can suspend but don't automatically run on background threads
- Actors protect mutable shared state through automatic synchronisation
@MainActorensures UI-related code executes on the main thread- Global actor-isolated types are automatically
Sendable
Essential Patterns
Async/Await
// Parallel execution with async let
func fetchData() async -> (String, Int) {
async let stringData = fetchString()
async let intData = fetchInt()
return await (stringData, intData)
}
// Always check cancellation in long-running operations
func process(_ items: [Item]) async throws -> [Result] {
var results: [Result] = []
for item in items {
try Task.checkCancellation()
results.append(await process(item))
}
return results
}
MainActor for UI Code
// Apply at type level for consistent isolation
@MainActor
class ContentViewModel: ObservableObject {
@Published var images: [UIImage] = []
func fetchData() async throws {
self.images = try await fetchImages()
}
}
// Avoid MainActor.run when direct await works
await doMainActorStuff() // Good
await MainActor.run { doMainActorStuff() } // Unnecessary
Actor Isolation
actor DataCache {
private var cache: [String: Data] = [:]
func store(_ data: Data, forKey key: String) {
cache[key] = data // No await needed inside actor
}
nonisolated func cacheType() -> String {
return "DataCache" // No await needed - doesn't access isolated state
}
}
Common Pitfalls to Avoid
- Don't mark functions as
asyncunnecessarily - async calling convention has overhead - Never use
DispatchSemaphorewith async/await - risk of deadlock - Don't create stateless actors - use non-isolated async functions instead
- Avoid split isolation - don't mix isolation domains within one type
- Check task cancellation - long operations must check
Task.checkCancellation() - Don't assume async means background - explicitly move work to background if needed
- Avoid excessive context switching - group operations within same isolation domain
API Design Quick Reference
Naming Conventions
- Types/protocols:
UpperCamelCase - Everything else:
lowerCamelCase - Protocols describing capabilities:
-able,-ible,-ingsuffixes (Equatable,ProgressReporting) - Factory methods: Begin with
make(x.makeIterator()) - Mutating pairs: imperative vs past participle (
x.sort()/x.sorted())
Method Naming by Side Effects
- No side effects: Noun phrases (
x.distance(to: y)) - With side effects: Imperative verbs (
x.append(y),x.sort())
Argument Labels
- Omit when arguments can't be distinguished:
min(number1, number2) - Value-preserving conversions omit first label:
Int64(someUInt32) - Prepositional phrases label at preposition:
x.removeBoxes(havingLength: 12) - Label all other arguments
Swift 6 Breaking Changes
Must Explicitly Mark Types with @MainActor (SE-0401)
Property wrappers no longer infer actor isolation automatically.
@MainActor
struct LogInView: View {
@StateObject private var model = ViewModel()
}
Global Variables Must Be Concurrency-Safe (SE-0412)
static let config = Config() // Constant - OK
@MainActor static var state = State() // Actor-isolated - OK
nonisolated(unsafe) var cache = [String: Data]() // Unsafe - use with caution
Other Changes
@UIApplicationMain/@NSApplicationMaindeprecated (use@main)anyrequired for existential types- Import visibility requires explicit access control
API Availability Patterns
// Basic availability
@available(macOS 15, iOS 18, *)
func modernAPI() { }
// Deprecation with message
@available(*, deprecated, message: "Use newMethod() instead")
func oldMethod() { }
// Renaming with auto-fix
@available(*, unavailable, renamed: "newMethod")
func oldMethod() { }
// Runtime checking
if #available(iOS 18, *) {
// iOS 18+ code
}
// Inverted checking (Swift 5.6+)
if #unavailable(iOS 18, *) {
// iOS 17 and lower
}
Key differences:
deprecated- Warning, allows usageobsoleted- Error from specific versionunavailable- Error, completely prevents usage
How to Use This Skill
When Writing Code
- Apply naming conventions following role-based, clarity-first principles
- Use appropriate isolation (
@MainActorfor UI, actors for mutable state) - Implement async/await patterns correctly with proper cancellation handling
- Follow Swift 6 concurrency model - trust compiler's flow analysis
- Document public APIs with clear, concise summaries
When Reviewing Code
- Check for concurrency safety violations
- Verify proper actor isolation and Sendable conformance
- Ensure async functions handle cancellation appropriately
- Validate API naming follows Swift guidelines
- Confirm availability annotations are correct for target platforms
Code Quality Standards
- Minimise comments - code should be self-documenting where possible
- Avoid over-engineering and unnecessary abstractions
- Use meaningful variable names based on role, not type
- Follow established project architecture and patterns
- Prefer
count(where:)overfilter().count - Use
InlineArrayfor fixed-size, performance-critical data - Trust compiler's concurrency flow analysis - avoid unnecessary
Sendableconformances
Resources
references/
Detailed reference material to load when in-depth information is needed:
- api-design.md - Complete API design conventions, documentation standards, parameter guidelines, and naming patterns
- concurrency.md - Detailed async/await patterns, actor best practices, common pitfalls, performance considerations, and thread safety patterns
- swift6-features.md - New language features in Swift 6/6.2, breaking changes, migration strategies, and modern patterns
- availability-patterns.md - Comprehensive
@availableattribute usage, deprecation strategies, and platform version management
Load these references when detailed information is needed beyond the core guidelines provided above.
Platform Requirements
- Swift 6.0+ compiler for Swift 6 features
- Swift 6.2+ for InlineArray and enhanced concurrency features
- macOS 15.7+ with appropriate SDK
- iOS 18+ for latest platform features
- Use
#availablefor runtime platform detection - Use
@availablefor API availability marking