Skip to content
Back to skills

Swift Standards

ASecurity

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.

  • 57 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 17, 2026
toolspythonrustgoswiftshellbashtestingdebugginggitapi

Works with

  • terminal
  • cli
  • api

Security analysis

A100/100

Scanned October 17, 2026

npx -y skills add diegosouzapw/awesome-omni-skill --skill swift-standards --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Swift Standards?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Swift Standards
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/diegosouzapw-swift-standards/badge)](https://www.skillsdirectory.com/skills/diegosouzapw-swift-standards)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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

1. **Package Management**: Swift Package Manager (SPM) exclusively
2. **Type Safety**: Strict concurrency, avoid Any, leverage generics
3. **Error Handling**: Typed throws (Swift 6), Result types
4. **State Modeling**: Enums with associated values for impossible states
5. **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

```swift
// swift-tools-version: 6.0
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

```bash
# Create new package
swift package init --type library
swift package init --type executable

# Build
swift build

# Run tests
swift test

# Run executable
swift run cli

# Update dependencies
swift package update

# Generate Xcode project (if needed)
swift package generate-xcodeproj
```

## Compiler Settings

### Strict Concurrency (Swift 6)

Enable strict concurrency checking in Package.swift:

```swift
.target(
    name: "Core",
    dependencies: [],
    swiftSettings: [
        .enableExperimentalFeature("StrictConcurrency")
    ]
)
```

Or in Xcode: Build Settings → Swift Compiler → Strict Concurrency Checking = Complete

### Treat Warnings as Errors

```swift
swiftSettings: [
    .unsafeFlags(["-warnings-as-errors"])
]
```

## Immutability by Default

### let Over var

```swift
// BAD
var name = "Kevin"
var count = 0

// GOOD - use let unless mutation is required
let name = "Kevin"
var count = 0  // Only if actually mutated later
```

### Structs Over Classes

```swift
// BAD - using class for simple data
class User {
    var id: UUID
    var name: String

    init(id: UUID, name: String) {
        self.id = id
        self.name = name
    }
}

// GOOD - struct for value types
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

```swift
// BAD - crashes if nil
let name = user.name!
let first = array.first!

// GOOD - guard let for early exit
guard let name = user.name else {
    return
}

// GOOD - if let for conditional
if let first = array.first {
    process(first)
}

// GOOD - nil coalescing
let name = user.name ?? "Unknown"

// GOOD - optional chaining
let count = user.orders?.count ?? 0
```

### Avoid Implicitly Unwrapped Optionals

```swift
// BAD
var delegate: MyDelegate!

// GOOD - make it explicit optional or non-optional
var delegate: MyDelegate?
// or
let delegate: MyDelegate  // Set in init
```

**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

```swift
// BAD - bag of optionals
struct LoadingState {
    var isLoading: Bool
    var data: Data?
    var error: Error?
}

// GOOD - impossible states are impossible
enum LoadingState<T> {
    case idle
    case loading
    case success(T)
    case failure(Error)
}

// Usage
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

```swift
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:

```swift
// Compiler error if you miss a case
switch state {
case .idle: break
case .loading: break
case .success: break
// Missing .failure - won't compile
}
```

## Error Handling

### Typed Throws (Swift 6)

```swift
// Define specific error types
enum ValidationError: Error {
    case emptyName
    case invalidEmail(String)
    case ageTooLow(minimum: Int)
}

// Typed throws - callers know exactly what can fail
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)
}

// Caller gets typed error
do {
    let user = try validate(user: input)
} catch {
    // error is ValidationError, not any Error
    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:

```swift
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))
    }
}

// Usage
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

```swift
// BAD - callback hell
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))
        }
    }
}

// GOOD - async/await
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

```swift
// BAD - manual locking
class Counter {
    private var value = 0
    private let lock = NSLock()

    func increment() {
        lock.lock()
        value += 1
        lock.unlock()
    }
}

// GOOD - actor handles synchronization
actor Counter {
    private var value = 0

    func increment() {
        value += 1
    }

    func getValue() -> Int {
        value
    }
}

// Usage
let counter = Counter()
await counter.increment()
let value = await counter.getValue()
```

### Task Groups for Parallel Work

```swift
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

```swift
// BAD
func process(data: Any) {
    if let string = data as? String {
        // ...
    }
}

// GOOD - use generics
func process<T: Processable>(data: T) {
    data.process()
}

// GOOD - use protocols
func process(data: some Processable) {
    data.process()
}
```

### Use Generics

```swift
// BAD - separate implementations
func firstString(in array: [String]) -> String? { array.first }
func firstInt(in array: [Int]) -> Int? { array.first }

// GOOD - generic
func first<T>(in array: [T]) -> T? {
    array.first
}
```

### Phantom Types for Type Safety

```swift
// Prevent mixing IDs of different entity types
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>
}

// Compiler prevents: order.id == user.id (different types)
```

## Project Structure

### Shared Core for Multi-Target

```text
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

```swift
// Sources/Core/Services/UserService.swift
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

```swift
// Sources/AppUI/Views/UserView.swift
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

```swift
// Sources/CLI/Main.swift
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

```swift
struct ContentView: View {
    @State private var count = 0

    var body: some View {
        VStack {
            Text("Count: \(count)")
            Button("Increment") {
                count += 1
            }
        }
    }
}
```

### Extract Subviews

```swift
// BAD - massive view
struct UserProfileView: View {
    var body: some View {
        VStack {
            // 200 lines of UI code
        }
    }
}

// GOOD - composed from smaller views
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

```swift
// Define dependency
struct UserServiceKey: EnvironmentKey {
    static let defaultValue: UserService = .live
}

extension EnvironmentValues {
    var userService: UserService {
        get { self[UserServiceKey.self] }
        set { self[UserServiceKey.self] = newValue }
    }
}

// Use in view
struct UserView: View {
    @Environment(\.userService) var userService

    var body: some View {
        // ...
    }
}

// Inject for testing
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

```bash
# Interpreted - slow startup (~500ms cold)
#!/usr/bin/swift
import Foundation
print(FileManager.default.currentDirectoryPath)

# Compiled - fast, but requires build step
swift build -c release
.build/release/my-script

# For scripts with dependencies, use swift-sh
brew install swift-sh
```

### swift-sh for Dependencies

```swift
#!/usr/bin/swift sh
import ArgumentParser  // @apple/swift-argument-parser ~> 1.3
import Rainbow         // @onevcat/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

```swift
import Foundation

let fm = FileManager.default
let home = fm.homeDirectoryForCurrentUser

// List directory
let contents = try fm.contentsOfDirectory(at: home, includingPropertiesForKeys: nil)

// Check existence
if fm.fileExists(atPath: "/tmp/file.txt") { }

// Create directory
try fm.createDirectory(at: home.appendingPathComponent("Scripts"),
                       withIntermediateDirectories: true)

// Copy/move/delete
try fm.copyItem(at: source, to: destination)
try fm.moveItem(at: source, to: destination)
try fm.removeItem(at: path)

// Attributes
let attrs = try fm.attributesOfItem(atPath: path)
let size = attrs[.size] as? UInt64
```

#### Process - Run Shell Commands

```swift
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) ?? ""
}

// Usage
let output = try shell("ls -la")
let gitStatus = try shell("git status --porcelain")
```

#### Async Process Execution

```swift
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

```swift
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)!

    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrService as String: service,
        kSecAttrAccount as String: account,
        kSecValueData as String: data
    ]

    // Delete existing
    SecItemDelete(query as CFDictionary)

    // Add new
    let status = SecItemAdd(query as CFDictionary, nil)
    guard status == errSecSuccess else {
        throw KeychainError.unableToStore
    }
}
```

#### NSWorkspace - App Control

```swift
import AppKit

let workspace = NSWorkspace.shared

// Open file with default app
workspace.open(URL(fileURLWithPath: "/path/to/file.pdf"))

// Open with specific app
workspace.open([URL(fileURLWithPath: "/path/to/file.txt")],
               withApplicationAt: URL(fileURLWithPath: "/Applications/Sublime Text.app"),
               configuration: .init())

// Launch app
workspace.launchApplication("Safari")

// Get running apps
let runningApps = workspace.runningApplications
for app in runningApps where app.isActive {
    print(app.localizedName ?? "Unknown")
}

// Activate app
if let app = runningApps.first(where: { $0.bundleIdentifier == "com.apple.Safari" }) {
    app.activate()
}

// Get frontmost app
if let frontmost = workspace.frontmostApplication {
    print(frontmost.localizedName ?? "")
}
```

#### FSEvents - File Watching

```swift
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 }
                // Call back on main thread
                DispatchQueue.main.async {
                    callback(paths)
                }
            },
            &context,
            paths as CFArray,
            FSEventStreamEventId(kFSEventStreamEventIdSinceNow),
            1.0,  // Latency in seconds
            flags
        )

        FSEventStreamScheduleWithRunLoop(stream!, CFRunLoopGetMain(), CFRunLoopMode.defaultMode.rawValue)
        FSEventStreamStart(stream!)
    }

    func stop() {
        if let stream = stream {
            FSEventStreamStop(stream)
            FSEventStreamInvalidate(stream)
            FSEventStreamRelease(stream)
        }
    }
}

// Usage
let watcher = FileWatcher()
watcher.watch(paths: ["~/Downloads"]) { changedPaths in
    for path in changedPaths {
        print("Changed: \(path)")
    }
}
RunLoop.main.run()  // Keep script alive
```

#### Accessibility - UI Automation

```swift
import ApplicationServices

// Check accessibility permissions
func checkAccessibilityPermissions() -> Bool {
    let options = [kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String: true]
    return AXIsProcessTrustedWithOptions(options as CFDictionary)
}

// Get focused element
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)
}

// Click menu item
func clickMenuItem(app: String, menu: String, item: String) {
    guard let runningApp = NSWorkspace.shared.runningApplications.first(where: {
        $0.localizedName == app
    }) else { return }

    let appElement = AXUIElementCreateApplication(runningApp.processIdentifier)
    // Navigate menu bar → menu → item and perform press action
    // ... (implementation depends on specific needs)
}
```

#### UserDefaults for Script Config

```swift
import Foundation

// Read/write to app defaults
let defaults = UserDefaults.standard
defaults.set("value", forKey: "myScriptSetting")
let setting = defaults.string(forKey: "myScriptSetting")

// Read other app's defaults (sandboxing permitting)
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
```

```swift
// Package.swift
// swift-tools-version: 6.0
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

```bash
#!/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:

```swift
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 {
            // Machine-readable: structured, no formatting
            let encoder = JSONEncoder()
            encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
            let data = try encoder.encode(users)
            print(String(data: data, encoding: .utf8)!)
        } else {
            // Human-readable: colors, pleasant
            print("\nUsers (\(users.count)):".bold)
            for user in users {
                print("  \(user.name.cyan) <\(user.email)>")
            }
            print()
        }
    }
}
```

**Rules:**
1. `--json` flag on every command that outputs data
2. JSON output: Codable structs, `prettyPrinted`, no ANSI
3. Default output: human-readable with Rainbow colors
4. 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.

```swift
// BAD - piecemeal imports for macOS
import Foundation
import AppKit
import CoreGraphics

// GOOD - single Cocoa import covers all macOS frameworks
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)

```swift
import os
import Cocoa

/// Centralized log categories per subsystem.
/// Each module gets its own category for filtered tailing.
enum Log {
    /// Subsystem matches bundle identifier for os_log filtering
    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")
}

// Usage - rich context at every call site
func fetchUser(id: UUID) async throws -> User {
    Log.network.info("⬆️ fetchUser started — id=\(id.uuidString, privacy: .public)")
    let start = ContinuousClock.now

    do {
        let user = try await api.get("/users/\(id)")
        let elapsed = ContinuousClock.now - start
        Log.network.info("⬇️ fetchUser success — id=\(id.uuidString, privacy: .public) elapsed=\(elapsed)")
        return user
    } catch {
        let elapsed = ContinuousClock.now - start
        Log.network.error("❌ fetchUser failed — id=\(id.uuidString, privacy: .public) elapsed=\(elapsed) error=\(error.localizedDescription, privacy: .public)")
        throw error
    }
}

// State transitions are always logged
func handleStateChange(from old: AppState, to new: AppState) {
    Log.lifecycle.notice("🔄 state transition — from=\(String(describing: old), privacy: .public) to=\(String(describing: new), privacy: .public)")
}
```

### Logging Rules

1. **Every public function logs entry and exit** — include parameters and elapsed time
2. **Errors always log full context** — what was attempted, with what inputs, what failed
3. **State transitions are always logged** — old state → new state
4. **Use emoji prefixes** for visual scanning: ⬆️ request, ⬇️ response, ❌ error, 🔄 transition, ✅ success, ⚠️ warning
5. **Include timing** — `ContinuousClock` for elapsed durations on any I/O or async operation
6. **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:

```swift
import Foundation

/// File-based logger for CLI tools.
/// Writes structured log lines to ~/.local/log/<tool>.log
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")
    }

    /// Append a structured log line.
    /// - Parameters:
    ///   - level: Log severity (debug, info, warn, error)
    ///   - category: Subsystem category for filtering
    ///   - message: Human-readable log message
    func log(_ level: String, category: String, _ message: String) {
        let timestamp = dateFormatter.string(from: Date())
        let line = "\(timestamp) [\(level.uppercased())] [\(category)] \(message)\n"

        // Also print to stderr so it doesn't pollute stdout (pipe-friendly)
        FileHandle.standardError.write(Data(line.utf8))

        // Append to file for tailing
        if let handle = try? FileHandle(forWritingTo: logFile) {
            handle.seekToEndOfFile()
            handle.write(Data(line.utf8))
            handle.closeFile()
        } else {
            try? line.data(using: .utf8)?.write(to: logFile)
        }
    }
}

// Usage
await FileLog.shared.log("info", category: "network", "⬆️ fetchUser id=\(id)")
```

## 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)

```bash
# Tail all logs from your app's subsystem
log stream --predicate 'subsystem == "com.yourapp.dev"' --level debug

# Filter to specific category
log stream --predicate 'subsystem == "com.yourapp.dev" AND category == "network"' --level debug

# With timestamps and process info
log stream --predicate 'subsystem == "com.yourapp.dev"' --level debug --style compact
```

### Tailing File Logs (CLI tools)

```bash
# Tail the log file for a CLI tool
tail -f ~/.local/log/my-tool.log

# Tail with grep for specific category
tail -f ~/.local/log/my-tool.log | grep '\[NETWORK\]'

# Tail all tool logs
tail -f ~/.local/log/*.log
```

### Development Workflow

1. **Start log tail first** — before running the app or script
2. **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
   ```
3. **Check `task_output`** periodically to review log output
4. **Filter aggressively** — use category predicates to reduce noise
5. **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

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.