Awesome-omni-skill swift-standards
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.
git clone https://github.com/diegosouzapw/awesome-omni-skill
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/development/swift-standards" ~/.claude/skills/diegosouzapw-awesome-omni-skill-swift-standards && rm -rf "$T"
skills/development/swift-standards/SKILL.mdSwift 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
// 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
# 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:
.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
// 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
// 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
// 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
// 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
// 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
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:
// 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)
// 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:
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
// 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
// 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
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
// 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
// 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
// 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
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
// 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
// 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
// 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
struct ContentView: View { @State private var count = 0 var body: some View { VStack { Text("Count: \(count)") Button("Increment") { count += 1 } } } }
Extract Subviews
// 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
// 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
# 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
#!/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
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
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
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)! 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
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
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
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
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
// 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
#!/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 { // 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:
flag on every command that outputs data--json- JSON output: Codable structs,
, no ANSIprettyPrinted - Default output: human-readable with Rainbow colors
- Use
in CommandConfiguration to explain what the command returnsdiscussion
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.
// 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) →
+ platform-specific importsimport Foundation - 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 /// 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
- 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 —
for elapsed durations on any I/O or async operationContinuousClock - Privacy-aware — use
only for non-sensitive data; defaults to redacted in release.public
File Logging for CLI Tools
For command-line tools where OSLog isn't practical, write to a known log file:
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)
# 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)
# 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
- Start log tail first — before running the app or script
- Use
to run the tail in the background when working in pi:bg_bashbg_bash: log stream --predicate 'subsystem == "com.yourapp.dev"' --level debug - Check
periodically to review log outputtask_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 | over |
| Value types | over |
| Optionals | , , (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 ) |
| Architecture | Shared Core for multi-target |
| macOS imports | (not piecemeal) |
| Logging | OSLog with categories (apps), FileLog (CLI) |
| Log tailing | Always run or 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