AgentSkillsCN

swift-standards

所有 Swift 输出——无论是文件还是对话片段——均需严格遵循规范。涵盖 SPM 包管理、严格并发模型、Result 类型、带关联值的枚举、let 优于 var、async/await 以及 SwiftUI 等特性。触发条件为任何 Swift 代码、iOS 应用、macOS 应用、Package.swift 文件或 Xcode 项目。无一例外。

SKILL.md
--- frontmatter
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:

code
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

PurposePackage
ColorsRainbow
Argument parsingswift-argument-parser
Progress/spinnersNo 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

Quick Reference

ToolPurpose
SPMPackage management (not CocoaPods, Carthage)
swift buildCompile
swift testRun tests
swift runExecute
PatternPreference
Mutabilitylet over var
Value typesstruct over class
Optionalsguard let, if let, ?? (never !)
State modelingEnums with associated values
Error handlingTyped throws (Swift 6), Result
Concurrencyasync/await, actors (not callbacks)
Type safetyGenerics, protocols (avoid Any)
ArchitectureShared Core for multi-target

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