| name | swift-standards |
| description | MANDATORY for ALL Swift output - files AND conversational snippets. Covers SPM, strict concurrency, Result types, enums with associated values, let over var, async/await, SwiftUI. Trigger: any Swift code, iOS, macOS, Package.swift, Xcode projects. No exceptions. |
Swift Best Practices
When to Use This Skill
This skill should be triggered when:
- Writing or reviewing Swift code
- Setting up Swift packages or Xcode projects
- Working with iOS, macOS, watchOS, tvOS, or visionOS
- Discussing Swift patterns, concurrency, or architecture
- Configuring Package.swift or build settings
Core Capabilities
- Package Management: Swift Package Manager (SPM) exclusively
- Type Safety: Strict concurrency, avoid Any, leverage generics
- Error Handling: Typed throws (Swift 6), Result types
- State Modeling: Enums with associated values for impossible states
- Concurrency: async/await, actors, structured concurrency
Package Management with SPM
Why SPM
- Built into Swift toolchain and Xcode
- No external dependencies (unlike CocoaPods, Carthage)
- First-class support for Swift concurrency
- Better security (no arbitrary scripts)
Package.swift Structure
import PackageDescription
let package = Package(
name: "MyApp",
platforms: [
.iOS(.v17),
.macOS(.v14)
],
products: [
.library(name: "Core", targets: ["Core"]),
.executable(name: "cli", targets: ["CLI"])
],
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0"),
.package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.0.0")
],
targets: [
.target(
name: "Core",
dependencies: [
.product(name: "Dependencies", package: "swift-dependencies")
]
),
.target(
name: "AppUI",
dependencies: ["Core"]
),
.executableTarget(
name: "CLI",
dependencies: [
"Core",
.product(name: "ArgumentParser", package: "swift-argument-parser")
]
),
.testTarget(
name: "CoreTests",
dependencies: ["Core"]
)
]
)
Common Commands
swift package init --type library
swift package init --type executable
swift build
swift test
swift run cli
swift package update
swift package generate-xcodeproj
Compiler Settings
Strict Concurrency (Swift 6)
Enable strict concurrency checking in Package.swift:
.target(
name: "Core",
dependencies: [],
swiftSettings: [
.enableExperimentalFeature("StrictConcurrency")
]
)
Or in Xcode: Build Settings → Swift Compiler → Strict Concurrency Checking = Complete
Treat Warnings as Errors
swiftSettings: [
.unsafeFlags(["-warnings-as-errors"])
]
Immutability by Default
let Over var
var name = "Kevin"
var count = 0
let name = "Kevin"
var count = 0
Structs Over Classes
class User {
var id: UUID
var name: String
init(id: UUID, name: String) {
self.id = id
self.name = name
}
}
struct User {
let id: UUID
let name: String
}
Use classes only when:
- You need reference semantics (shared mutable state)
- You need inheritance
- You need deinit
- You're interfacing with Objective-C
Optionals
Never Force Unwrap
let name = user.name!
let first = array.first!
guard let name = user.name else {
return
}
if let first = array.first {
process(first)
}
let name = user.name ?? "Unknown"
let count = user.orders?.count ?? 0
Avoid Implicitly Unwrapped Optionals
var delegate: MyDelegate!
var delegate: MyDelegate?
let delegate: MyDelegate
Exception: @IBOutlet in UIKit (required by Interface Builder)
Enums with Associated Values
Swift enums are powerful discriminated unions. Use them to make invalid states unrepresentable:
State Modeling
struct LoadingState {
var isLoading: Bool
var data: Data?
var error: Error?
}
enum LoadingState<T> {
case idle
case loading
case success(T)
case failure(Error)
}
func render(state: LoadingState<User>) {
switch state {
case .idle:
showPlaceholder()
case .loading:
showSpinner()
case .success(let user):
showUser(user)
case .failure(let error):
showError(error)
}
}
Events
enum UserEvent {
case created(User)
case updated(User, changes: [String: Any])
case deleted(id: UUID)
}
func handle(event: UserEvent) {
switch event {
case .created(let user):
notifyCreation(user)
case .updated(let user, let changes):
notifyUpdate(user, changes: changes)
case .deleted(let id):
notifyDeletion(id)
}
}
Exhaustive Switch
Always handle all cases - compiler enforces this:
switch state {
case .idle: break
case .loading: break
case .success: break
}
Error Handling
Typed Throws (Swift 6)
enum ValidationError: Error {
case emptyName
case invalidEmail(String)
case ageTooLow(minimum: Int)
}
func validate(user: UserInput) throws(ValidationError) -> User {
guard !user.name.isEmpty else {
throw .emptyName
}
guard user.email.contains("@") else {
throw .invalidEmail(user.email)
}
guard user.age >= 18 else {
throw .ageTooLow(minimum: 18)
}
return User(name: user.name, email: user.email, age: user.age)
}
do {
let user = try validate(user: input)
} catch {
switch error {
case .emptyName:
showNameError()
case .invalidEmail(let email):
showEmailError(email)
case .ageTooLow(let minimum):
showAgeError(minimum)
}
}
Result Type
For async operations or when you want to pass errors as values:
func fetchUser(id: UUID) async -> Result<User, NetworkError> {
do {
let data = try await network.get("/users/\(id)")
let user = try decoder.decode(User.self, from: data)
return .success(user)
} catch let error as NetworkError {
return .failure(error)
} catch {
return .failure(.unknown(error))
}
}
let result = await fetchUser(id: userId)
switch result {
case .success(let user):
display(user)
case .failure(let error):
handleError(error)
}
Concurrency
async/await Over Callbacks
func fetchUser(id: UUID, completion: @escaping (Result<User, Error>) -> Void) {
network.get("/users/\(id)") { result in
switch result {
case .success(let data):
do {
let user = try decoder.decode(User.self, from: data)
completion(.success(user))
} catch {
completion(.failure(error))
}
case .failure(let error):
completion(.failure(error))
}
}
}
func fetchUser(id: UUID) async throws -> User {
let data = try await network.get("/users/\(id)")
return try decoder.decode(User.self, from: data)
}
Actors for Shared Mutable State
class Counter {
private var value = 0
private let lock = NSLock()
func increment() {
lock.lock()
value += 1
lock.unlock()
}
}
actor Counter {
private var value = 0
func increment() {
value += 1
}
func getValue() -> Int {
value
}
}
let counter = Counter()
await counter.increment()
let value = await counter.getValue()
Task Groups for Parallel Work
func fetchAllUsers(ids: [UUID]) async throws -> [User] {
try await withThrowingTaskGroup(of: User.self) { group in
for id in ids {
group.addTask {
try await fetchUser(id: id)
}
}
var users: [User] = []
for try await user in group {
users.append(user)
}
return users
}
}
Type Safety
Avoid Any
func process(data: Any) {
if let string = data as? String {
}
}
func process<T: Processable>(data: T) {
data.process()
}
func process(data: some Processable) {
data.process()
}
Use Generics
func firstString(in array: [String]) -> String? { array.first }
func firstInt(in array: [Int]) -> Int? { array.first }
func first<T>(in array: [T]) -> T? {
array.first
}
Phantom Types for Type Safety
struct ID<Entity>: Hashable {
let rawValue: UUID
}
struct User {
let id: ID<User>
let name: String
}
struct Order {
let id: ID<Order>
let userId: ID<User>
}
Project Structure
Shared Core for Multi-Target
MyApp/
├── Package.swift
├── Sources/
│ ├── Core/ # Shared business logic
│ │ ├── Models/
│ │ │ └── User.swift
│ │ ├── Services/
│ │ │ └── UserService.swift
│ │ └── Database/
│ │ └── Repository.swift
│ ├── AppUI/ # SwiftUI views import Core
│ │ ├── App.swift
│ │ └── Views/
│ │ └── UserView.swift
│ └── CLI/ # ArgumentParser commands import Core
│ └── Main.swift
└── Tests/
└── CoreTests/
└── UserServiceTests.swift
Example Core Module
public struct UserService {
private let repository: UserRepository
public init(repository: UserRepository) {
self.repository = repository
}
public func createUser(name: String, email: String) async throws -> User {
let user = User(id: ID(rawValue: UUID()), name: name, email: email)
try await repository.save(user)
return user
}
}
Example SwiftUI Using Core
import SwiftUI
import Core
struct UserView: View {
let userService: UserService
@State private var state: LoadingState<User> = .idle
var body: some View {
switch state {
case .idle:
Button("Load") { Task { await load() } }
case .loading:
ProgressView()
case .success(let user):
Text(user.name)
case .failure(let error):
Text(error.localizedDescription)
}
}
private func load() async {
state = .loading
do {
let user = try await userService.fetchUser()
state = .success(user)
} catch {
state = .failure(error)
}
}
}
Example CLI Using Core
import ArgumentParser
import Core
@main
struct CLI: AsyncParsableCommand {
static let configuration = CommandConfiguration(
commandName: "myapp",
subcommands: [CreateUser.self]
)
}
struct CreateUser: AsyncParsableCommand {
@Argument var name: String
@Argument var email: String
func run() async throws {
let service = UserService(repository: .live)
let user = try await service.createUser(name: name, email: email)
print("Created user: \(user.id.rawValue)")
}
}
SwiftUI Patterns
View as Function of State
struct ContentView: View {
@State private var count = 0
var body: some View {
VStack {
Text("Count: \(count)")
Button("Increment") {
count += 1
}
}
}
}
Extract Subviews
struct UserProfileView: View {
var body: some View {
VStack {
}
}
}
struct UserProfileView: View {
let user: User
var body: some View {
VStack {
AvatarView(url: user.avatarURL)
UserInfoSection(user: user)
UserStatsSection(stats: user.stats)
}
}
}
Dependency Injection with Environment
struct UserServiceKey: EnvironmentKey {
static let defaultValue: UserService = .live
}
extension EnvironmentValues {
var userService: UserService {
get { self[UserServiceKey.self] }
set { self[UserServiceKey.self] = newValue }
}
}
struct UserView: View {
@Environment(\.userService) var userService
var body: some View {
}
}
UserView()
.environment(\.userService, .mock)
macOS Scripting
When to Use Swift for Scripts
Use Swift when:
- You need macOS APIs (Keychain, Accessibility, FSEvents, XPC, IOKit)
- Performance matters (image processing, large file ops)
- You want to distribute a binary without dependencies
Use Python/shell when:
- Quick automation, text processing
- Cross-platform needed
- Rapid iteration more important than type safety
Script Execution Modes
import Foundation
print(FileManager.default.currentDirectoryPath)
swift build -c release
.build/release/my-script
brew install swift-sh
swift-sh for Dependencies
#!/usr/bin/swift sh
import ArgumentParser
import Rainbow
@main
struct MyScript: ParsableCommand {
@Argument var name: String
func run() {
print("Hello, \(name)".green)
}
}
Run directly: ./my-script.swift Kevin
Common macOS APIs
FileManager - File Operations
import Foundation
let fm = FileManager.default
let home = fm.homeDirectoryForCurrentUser
let contents = try fm.contentsOfDirectory(at: home, includingPropertiesForKeys: nil)
if fm.fileExists(atPath: "/tmp/file.txt") { }
try fm.createDirectory(at: home.appendingPathComponent("Scripts"),
withIntermediateDirectories: true)
try fm.copyItem(at: source, to: destination)
try fm.moveItem(at: source, to: destination)
try fm.removeItem(at: path)
let attrs = try fm.attributesOfItem(atPath: path)
let size = attrs[.size] as? UInt64
Process - Run Shell Commands
import Foundation
func shell(_ command: String) throws -> String {
let process = Process()
let pipe = Pipe()
process.standardOutput = pipe
process.standardError = pipe
process.executableURL = URL(fileURLWithPath: "/bin/zsh")
process.arguments = ["-c", command]
try process.run()
process.waitUntilExit()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
return String(data: data, encoding: .utf8) ?? ""
}
let output = try shell("ls -la")
let gitStatus = try shell("git status --porcelain")
Async Process Execution
func shellAsync(_ command: String) async throws -> String {
try await withCheckedThrowingContinuation { continuation in
let process = Process()
let pipe = Pipe()
process.standardOutput = pipe
process.standardError = pipe
process.executableURL = URL(fileURLWithPath: "/bin/zsh")
process.arguments = ["-c", command]
process.terminationHandler = { _ in
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8) ?? ""
continuation.resume(returning: output)
}
do {
try process.run()
} catch {
continuation.resume(throwing: error)
}
}
}
Keychain Access
import Security
func getKeychainPassword(service: String, account: String) -> String? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account,
kSecReturnData as String: true
]
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
guard status == errSecSuccess,
let data = result as? Data,
let password = String(data: data, encoding: .utf8) else {
return nil
}
return password
}
func setKeychainPassword(service: String, account: String, password: String) throws {
let data password.data(using: .utf8)
query: [: ] [
kSecClass : kSecClassGenericPassword,
kSecAttrService : service,
kSecAttrAccount : account,
kSecValueData : data
]
(query )
status (query , )
status errSecSuccess {
.unableToStore
}
}
NSWorkspace - App Control
import AppKit
let workspace = NSWorkspace.shared
workspace.open(URL(fileURLWithPath: "/path/to/file.pdf"))
workspace.open([URL(fileURLWithPath: "/path/to/file.txt")],
withApplicationAt: URL(fileURLWithPath: "/Applications/Sublime Text.app"),
configuration: .init())
workspace.launchApplication("Safari")
let runningApps = workspace.runningApplications
for app in runningApps where app.isActive {
print(app.localizedName ?? "Unknown")
}
if let app = runningApps.first(where: { $0.bundleIdentifier == "com.apple.Safari" }) {
app.activate()
}
if let frontmost = workspace.frontmostApplication {
print(frontmost.localizedName ?? "")
}
FSEvents - File Watching
import Foundation
class FileWatcher {
private var stream: FSEventStreamRef?
func watch(paths: [String], callback: @escaping ([String]) -> Void) {
var context = FSEventStreamContext()
context.info = Unmanaged.passUnretained(self).toOpaque()
let flags = UInt32(kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagUseCFTypes)
stream = FSEventStreamCreate(
nil,
{ _, _, numEvents, eventPaths, _, _ in
guard let paths = unsafeBitCast(eventPaths, to: NSArray.self) as? [String] else { return }
DispatchQueue.main.async {
callback(paths)
}
},
&context,
paths as CFArray,
FSEventStreamEventId(kFSEventStreamEventIdSinceNow),
,
flags
)
(stream, (), .defaultMode.rawValue)
(stream)
}
() {
stream stream {
(stream)
(stream)
(stream)
}
}
}
watcher ()
watcher.watch(paths: []) { changedPaths
path changedPaths {
()
}
}
.main.run()
Accessibility - UI Automation
import ApplicationServices
func checkAccessibilityPermissions() -> Bool {
let options = [kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String: true]
return AXIsProcessTrustedWithOptions(options as CFDictionary)
}
func getFocusedElement() -> AXUIElement? {
let systemWide = AXUIElementCreateSystemWide()
var focusedElement: CFTypeRef?
let result = AXUIElementCopyAttributeValue(systemWide, kAXFocusedUIElementAttribute as CFString, &focusedElement)
guard result == .success else { return nil }
return (focusedElement as! AXUIElement)
}
func clickMenuItem(app: String, menu: String, item: String) {
guard let runningApp = NSWorkspace.shared.runningApplications.first(where: {
.localizedName app
}) { }
appElement (runningApp.processIdentifier)
}
UserDefaults for Script Config
import Foundation
let defaults = UserDefaults.standard
defaults.set("value", forKey: "myScriptSetting")
let setting = defaults.string(forKey: "myScriptSetting")
if let safariDefaults = UserDefaults(suiteName: "com.apple.Safari") {
let homepage = safariDefaults.string(forKey: "HomePage")
}
Script Project Structure
For non-trivial scripts, use a proper SPM package:
my-script/
├── Package.swift
├── Sources/
│ └── my-script/
│ ├── main.swift # Entry point
│ ├── Commands/ # ArgumentParser commands
│ └── Utilities/ # Shared helpers
└── scripts/
└── install.sh # Copy binary to ~/bin
import PackageDescription
let package = Package(
name: "my-script",
platforms: [.macOS(.v14)],
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0"),
.package(url: "https://github.com/onevcat/Rainbow", from: "4.0.0")
],
targets: [
.executableTarget(
name: "my-script",
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
"Rainbow"
]
)
]
)
Install Script
#!/bin/bash
swift build -c release
cp .build/release/my-script ~/bin/
CLI Output Libraries
| Purpose | Package |
|---|
| Colors | Rainbow |
| Argument parsing | swift-argument-parser |
| Progress/spinners | No good Swift option - use print-based |
Note: Swift CLI ecosystem is thinner than Python's. For complex TUI, consider whether Python (Rich, tqdm) is more practical.
LLM-Friendly Output
All CLIs must support both human and machine consumption:
import ArgumentParser
import Foundation
import Rainbow
struct User: Codable {
let id: String
let name: String
let email: String
}
struct ListUsers: AsyncParsableCommand {
static let configuration = CommandConfiguration(
abstract: "List all users.",
discussion: """
Returns array of user objects with id, name, and email fields.
Use --json for structured output suitable for piping to other tools or LLMs.
"""
)
@Flag(name: .long, help: "Output as JSON for programmatic consumption")
var json = false
@Option(name: .shortAndLong, help: "Maximum number of users to return")
var limit: Int = 50
func run() async throws {
let users = try await getUsers(limit: limit)
if json {
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
data encoder.encode(users)
((data: data, encoding: .utf8))
} {
(.bold)
user users {
()
}
()
}
}
}
Rules:
--json flag on every command that outputs data
- JSON output: Codable structs,
prettyPrinted, no ANSI
- Default output: human-readable with Rainbow colors
- Use
discussion in CommandConfiguration to explain what the command returns
Cocoa Framework
Always use Cocoa (import Cocoa) for macOS applications. Cocoa provides the full macOS application stack (AppKit, Foundation, CoreData) in a single import. For iOS, use UIKit/SwiftUI as appropriate.
import Foundation
import AppKit
import CoreGraphics
import Cocoa
When to use Cocoa vs individual imports:
- macOS apps and scripts →
import Cocoa
- Cross-platform packages (iOS + macOS) →
import Foundation + platform-specific imports
- Pure logic modules with no UI →
import Foundation
Self-Consuming Logging
Always create structured, self-consuming logging patterns for troubleshooting during development. Logs must be useful enough that you can diagnose issues by reading them alone — no debugger required.
OSLog (Preferred)
import os
import Cocoa
enum Log {
private static let subsystem = Bundle.main.bundleIdentifier ?? "com.app.dev"
static let network = Logger(subsystem: subsystem, category: "network")
static let database = Logger(subsystem: subsystem, category: "database")
static let ui = Logger(subsystem: subsystem, category: "ui")
static let lifecycle = Logger(subsystem: subsystem, category: "lifecycle")
static let auth = Logger(subsystem: subsystem, category: "auth")
}
func fetchUser(id: UUID) async throws -> User {
Log.network.info("⬆️ fetchUser started — id=\(id.uuidString, privacy: .public)")
start .now
{
user api.get()
elapsed .now start
.network.info()
user
} {
elapsed .now start
.network.error()
error
}
}
( : , : ) {
.lifecycle.notice()
}
Logging Rules
- Every public function logs entry and exit — include parameters and elapsed time
- Errors always log full context — what was attempted, with what inputs, what failed
- State transitions are always logged — old state → new state
- Use emoji prefixes for visual scanning: ⬆️ request, ⬇️ response, ❌ error, 🔄 transition, ✅ success, ⚠️ warning
- Include timing —
ContinuousClock for elapsed durations on any I/O or async operation
- Privacy-aware — use
.public only for non-sensitive data; defaults to redacted in release
File Logging for CLI Tools
For command-line tools where OSLog isn't practical, write to a known log file:
import Foundation
actor FileLog {
static let shared = FileLog()
private let logFile: URL
private let dateFormatter: ISO8601DateFormatter = {
let f = ISO8601DateFormatter()
f.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
return f
}()
private init() {
let logDir = FileManager.default.homeDirectoryForCurrentUser
.appendingPathComponent(".local/log")
try? FileManager.default.createDirectory(at: logDir, withIntermediateDirectories: true)
let processName = ProcessInfo.processInfo.processName
logFile = logDir.appendingPathComponent("\(processName).log")
}
func log(_ level: , : , : ) {
timestamp dateFormatter.string(from: ())
line
.standardError.write((line.utf8))
handle (forWritingTo: logFile) {
handle.seekToEndOfFile()
handle.write((line.utf8))
handle.closeFile()
} {
line.data(using: .utf8).write(to: logFile)
}
}
}
.shared.log(, category: , )
Always Tail Logs During Development
When developing or debugging Swift code, always run a log tail in a background terminal. This is non-negotiable — logs are useless if nobody is watching them.
Tailing OSLog (macOS apps)
log stream --predicate 'subsystem == "com.yourapp.dev"' --level debug
log stream --predicate 'subsystem == "com.yourapp.dev" AND category == "network"' --level debug
log stream --predicate 'subsystem == "com.yourapp.dev"' --level debug --style compact
Tailing File Logs (CLI tools)
tail -f ~/.local/log/my-tool.log
tail -f ~/.local/log/my-tool.log | grep '\[NETWORK\]'
tail -f ~/.local/log/*.log
Development Workflow
- Start log tail first — before running the app or script
- Use
bg_bash to run the tail in the background when working in pi:
bg_bash: log stream --predicate 'subsystem == "com.yourapp.dev"' --level debug
- Check
task_output periodically to review log output
- Filter aggressively — use category predicates to reduce noise
- Never ship without reviewing logs — if the log tail shows unexpected entries, investigate before committing
Quick Reference
| Tool | Purpose |
|---|
| SPM | Package management (not CocoaPods, Carthage) |
| swift build | Compile |
| swift test | Run tests |
| swift run | Execute |
| Pattern | Preference |
|---|
| Mutability | let over var |
| Value types | struct over class |
| Optionals | guard let, if let, ?? (never !) |
| State modeling | Enums with associated values |
| Error handling | Typed throws (Swift 6), Result |
| Concurrency | async/await, actors (not callbacks) |
| Type safety | Generics, protocols (avoid Any) |
| Architecture | Shared Core for multi-target |
| macOS imports | import Cocoa (not piecemeal) |
| Logging | OSLog with categories (apps), FileLog (CLI) |
| Log tailing | Always run log stream or tail -f during dev |
Notes
- Swift 6 requires strict concurrency - design for it from the start
- Enums with associated values are Swift's killer feature for state modeling
- SPM is the only package manager worth using in 2024+
- Prefer value types (struct, enum) over reference types (class)
- Use actors instead of manual locking for shared state
- SwiftUI is declarative - views should be pure functions of state