Swift Rules
These rules come from app/rules/swift/ in ai-toolkit. They cover
the project's standards for coding style, frameworks, patterns,
security, and testing in Swift. Apply them when writing or
reviewing Swift code.
Swift Coding Style
Naming
- PascalCase: types, protocols, enums, struct, class.
- camelCase: functions, methods, properties, variables, enum cases.
- No prefixes: Swift has module namespacing (no
NSorUIprefix for your types). - Use descriptive names:
removeElement(at:)notremove(i:). - Boolean properties read as assertions:
isEmpty,hasChildren,canSubmit.
Types
- Prefer
structoverclassby default (value semantics, no reference cycles). - Use
classonly when reference semantics or inheritance is required. - Use
enumwith associated values for modeling finite states. - Use
protocolfor defining capabilities. Prefer protocol composition. - Use
typealiasfor complex generic signatures for readability.
Optionals
- Use
guard letfor early exit on nil. Useif letfor conditional binding. - Never force-unwrap (
!) unless failure is a programming error. - Use
??for default values:let name = user?.name ?? "Unknown". - Use optional chaining:
user?.address?.city. - Use
map/flatMapon optionals for transformations.
Properties
- Use
letby default. Usevaronly when mutation is required. - Use computed properties for derived values:
var fullName: String { ... }. - Use property observers (
willSet,didSet) for side effects on change. - Use
lazy varfor expensive initialization deferred until first access. - Use
@Published(Combine) for observable properties in classes.
Functions
- Use argument labels for clarity:
func move(from source: Int, to destination: Int). - Omit argument labels when the function name makes the role clear:
func contains(_ element: T). - Use default parameter values instead of multiple overloads.
- Use
throws/async throwsfor fallible operations. - Use trailing closure syntax for the last closure parameter.
Access Control
- Use
privatefor implementation details. Usefileprivatesparingly. - Use
internal(default) for module-scoped access. - Use
publicfor framework API. Useopenonly when subclassing is intended. - Prefer
private(set)for read-only external access with internal mutation.
Formatting
- Use SwiftLint for automated style enforcement.
- Use SwiftFormat for automated code formatting.
- Commit
.swiftlint.ymland.swiftformatto the repository. - Max line length: 120 characters (SwiftLint default).
- Use trailing commas in multi-line arrays and dictionaries.
Swift Frameworks
SwiftUI
- Use
VStack,HStack,ZStackfor layout composition. - Use
ListwithForEachfor dynamic content. UseLazyVStackfor large lists. - Use
NavigationStack(iOS 16+) withnavigationDestination(for:)for type-safe navigation. - Use
.task { }modifier for async data loading tied to view lifecycle. - Use
@ViewBuilderfor conditional view composition in custom containers. - Use
PreviewProvideror#Previewmacro for rapid UI iteration.
UIKit (Legacy / Hybrid)
- Use
UIHostingControllerto embed SwiftUI views in UIKit. - Use
UIViewRepresentableto wrap UIKit views in SwiftUI. - Use Auto Layout with constraints or
UIStackViewfor layout. - Use
UICollectionViewCompositionalLayoutfor complex collection layouts. - Use
Coordinatorpattern for delegate-based UIKit interop in SwiftUI.
Combine
- Use
Publisher/Subscriberfor reactive data streams. - Use
sinkfor subscribing. Store cancellables inSet<AnyCancellable>. - Use
map,filter,flatMap,combineLatestfor stream transformation. - Use
@Publishedon class properties for automatic publisher generation. - Prefer
AsyncSequence(async/await) over Combine for new code.
Swift Data
- Use
@Modelmacro for persistent model definitions. - Use
@Queryin SwiftUI views for automatic fetching and observation. - Use
ModelContextfor CRUD operations:context.insert(item),context.delete(item). - Use
#Predicatemacro for type-safe query filtering. - Use
ModelConfigurationfor custom store locations and migration options.
Core Data (Legacy)
- Use
NSPersistentContainerfor stack setup. - Use
NSFetchRequestwithNSPredicatefor querying. - Use
performBackgroundTaskfor background context operations. - Use lightweight migrations for schema changes when possible.
- Prefer SwiftData for new projects (iOS 17+).
Vapor (Server-Side)
- Use
routes.get("users")for route definitions. - Use
Contentprotocol for request/response body codable conformance. - Use Fluent ORM with migrations for database access.
- Use middleware for authentication, CORS, and error handling.
- Use
async/awaitnatively (Vapor 4+ is fully async).
Networking
- Use
URLSessionwithasync/awaitfor HTTP requests. - Use
CodablewithJSONDecoderfor response parsing. - Use
URLCacheandETagfor response caching. - Set
timeoutIntervalForRequestonURLSessionConfiguration. - Use
TaskLocalfor request-scoped values (tracing, auth context).
Package Management
- Use Swift Package Manager (SPM) for dependency management.
- Define dependencies in
Package.swiftwith exact version or version ranges. - Use
Package.resolvedcommitted to the repository for reproducible builds. - Prefer SPM over CocoaPods/Carthage for new projects.
Swift Patterns
Error Handling
- Use
enum AppError: Errorfor typed, exhaustive error handling. - Use
throwsfunctions withdo-catchfor recoverable errors. - Use
Result<Success, Failure>for asynchronous error propagation. - Use
try?for optional conversion. Usetry!only in tests or guaranteed paths. - Add
LocalizedErrorconformance for user-facing error messages.
Protocol-Oriented Design
- Define capabilities as protocols:
protocol Fetchable { func fetch() async throws -> Data }. - Use protocol extensions for default implementations.
- Use protocol composition:
func process(_ item: Sendable & Codable). - Use associated types for generic protocols:
associatedtype Output. - Use
some Protocol(opaque types) for return types hiding concrete implementations.
Async/Await
- Use
asyncfunctions for all asynchronous operations. - Use
async letfor concurrent, independent operations. - Use
TaskGroupfor dynamic parallelism with collected results. - Use
Task { }to bridge sync to async. Avoid.task { }in views for complex logic. - Use
withThrowingTaskGroupfor concurrent operations that can fail.
Actors
- Use
actorfor thread-safe mutable state (replaces manual locks). - Use
@MainActorfor UI-related state and methods. - Use
nonisolatedfor actor methods that do not access mutable state. - Use
GlobalActorfor custom isolation domains. - Minimize
awaitcalls on actors to reduce suspension points.
SwiftUI Patterns
- Use
@Statefor view-local mutable state. - Use
@Bindingfor child-to-parent state communication. - Use
@Observable(Observation framework) for model objects (preferred over@ObservedObject). - Use
@Environmentfor dependency injection:@Environment(\.modelContext). - Use
ViewModifierfor reusable view transformations. - Extract subviews into separate structs for readability and performance.
Codable
- Use
Codablefor JSON serialization/deserialization. - Use
CodingKeysenum for custom key mapping. - Use
JSONDecoderwith.convertFromSnakeCasefor API compatibility. - Use
@propertyWrapperfor custom decoding strategies (e.g., date formats). - Use
nestedContainerfor flattening nested JSON structures.
Dependency Injection
- Use initializer injection for required dependencies.
- Use
@Environmentin SwiftUI for framework-provided values. - Use
swift-dependencieslibrary for testable, controlled dependency management. - Use
@Dependency(\.apiClient) var apiClientfor automatic resolution.
Anti-Patterns
- Force-unwrapping optionals: use
guard letor??. - Massive view controllers/views: split into subviews and view models.
- Reference cycles: use
[weak self]in closures capturingself. - Blocking the main thread: use
TaskorDispatchQueue.global(). - Stringly-typed APIs: use enums, protocols, and strong types.
Swift Security
Keychain
- Use Keychain Services for storing passwords, tokens, and cryptographic keys.
- Use
kSecAttrAccessibleWhenUnlockedThisDeviceOnlyfor sensitive items. - Use
KeychainAccessor similar wrapper libraries for cleaner API. - Never store secrets in
UserDefaults(unencrypted plist on disk). - Delete keychain items on user logout.
App Transport Security (ATS)
- Use HTTPS for all network connections. ATS enforces this by default.
- Never add blanket
NSAllowsArbitraryLoadsexception. - Use per-domain exceptions only when connecting to legacy servers.
- Implement certificate pinning for high-security connections.
- Validate server certificates in
URLSessionDelegatefor custom pinning.
Input Validation
- Validate all user input before processing or displaying.
- Use
NSRegularExpressionor Swift Regex for pattern validation. - Sanitize strings before using in URL construction, SQL, or HTML.
- Validate deep link URL parameters before navigation.
- Limit input lengths in
UITextField/TextFieldto prevent abuse.
Data Protection
- Use
Data ProtectionAPI: setFileProtectionType.completeon sensitive files. - Use
CryptoKitfor hashing (SHA256), encryption (AES.GCM), and signing. - Use
SecureEnclavefor hardware-backed key storage on supported devices. - Zero sensitive data in memory after use:
withUnsafeMutableBytes { $0.initializeMemory(as: UInt8.self, repeating: 0) }. - Use
@Sendableclosures to prevent data races in concurrent access.
Authentication
- Use
AuthenticationServicesfor Sign in with Apple and passkeys. - Use
LocalAuthentication(Face ID / Touch ID) for biometric auth. - Store authentication tokens in Keychain, not in memory or UserDefaults.
- Use short-lived access tokens with refresh token rotation.
- Implement session timeout for inactive users.
Network Security
- Use
URLSessionwith certificate pinning for sensitive API calls. - Validate response
Content-Typeheaders before parsing. - Use
Codablefor structured deserialization (prevents injection). - Set request timeouts to prevent hanging connections.
- Do not log request/response bodies containing sensitive data.
Code Security
- Use
[weak self]in closures to prevent retain cycles and memory leaks. - Use
@Sendableand actor isolation for thread-safe concurrent code. - Avoid
UnsafePointer/UnsafeMutablePointerunless absolutely necessary. - Use
#if DEBUGguards for debug-only code. Never ship debug features. - Enable Xcode hardened runtime for macOS apps.
Dependency Security
- Audit SPM dependencies before adding. Check maintainer reputation.
- Pin dependency versions in
Package.resolved. - Review
Package.swiftof dependencies for unusual build plugins. - Prefer dependencies with active security response and disclosure processes.
- Minimize third-party dependencies for security-critical modules.
Swift Testing
Framework
- Use Swift Testing (
import Testing) for new projects (Swift 5.10+). - Use XCTest for existing projects and UIKit-based UI tests.
- Use swift-snapshot-testing for visual regression testing.
- Use swift-dependencies for controlled dependency injection in tests.
File Naming
- Test files:
FooTests.swiftinTests/target. - Mirror source module structure in test target.
- Use
@Testattribute (Swift Testing) ortestprefix (XCTest) for test methods. - Use
@Suite(Swift Testing) for test grouping.
Structure (Swift Testing)
- Use
@Test("description")for individual test cases. - Use
@Test(arguments: [...])for parameterized tests. - Use
#expect(condition)for assertions. Use#require(condition)for preconditions. - Use
#expect(throws: FooError.self) { try riskyOperation() }for error testing. - Use
@Suitestructs for grouping. Properties serve as shared setup.
Structure (XCTest)
- Use
setUp()/tearDown()for per-test initialization and cleanup. - Use
setUpWithError()for throwing setup code. - Use
XCTAssertEqual,XCTAssertTrue,XCTAssertNilfor assertions. - Use
XCTAssertThrowsErrorfor exception testing. - Use
expectation(description:)+wait(for:timeout:)for async assertions.
Async Testing
- Use
asynctest functions:@Test func fetchUser() async throws { ... }. - Use
confirmation()(Swift Testing) for event-based async assertions. - XCTest: use
XCTestExpectationwithfulfillment()for callback-based async. - Test
AsyncSequencewithfor awaitloops and assertion on collected values.
Mocking
- Use protocol-based dependency injection for testability.
- Create manual mock implementations conforming to protocols.
- Use
swift-dependenciesfor environment-controlled dependency overrides. - Use
@Dependencyproperty wrapper for automatic mock injection in tests. - Avoid mocking frameworks when protocol mocks are straightforward.
UI Testing (XCTest)
- Use
XCUIApplicationfor UI automation tests. - Use accessibility identifiers for reliable element lookup.
- Use
app.buttons["Submit"].tap()for interaction simulation. - Use
waitForExistence(timeout:)for async UI element appearance. - Keep UI tests focused on critical user flows only (slow to run).
Best Practices
- Test behavior through public API. Avoid
@testable importwhen possible. - Use
@testable import Moduleonly when testing internal members is necessary. - Use
withDependencies { }for scoped dependency overrides in tests. - Test on multiple platforms (iOS, macOS) when shipping cross-platform.
- Run tests with
swift testorxcodebuild testin CI.