# Swift Expert

> Expert-level Swift development for iOS, macOS with SwiftUI, Combine, and modern Swift 5.9+. Use when the user mentions iOS, macOS, SwiftUI, Combine, async await, or Apple platforms, or when the task involves Modern Swift Features, Basics and Optionals, Functions and Closures, or Structs and Classes.

- Skill: `personamanagmentlayer/swift-expert` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds add personamanagmentlayer/swift-expert`
- Raw SKILL.md: https://api.skillmd.com/api/skills/personamanagmentlayer/swift-expert/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- Author: personamanagmentlayer (https://skillmd.com/u/personamanagmentlayer)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/personamanagmentlayer/swift-expert

---


# Swift Expert

Expert guidance for Swift development including iOS/macOS apps, SwiftUI, Combine, async/await, and Swift 5.9+ features.

## Core Concepts

### Modern Swift Features (5.9+)

- Async/await concurrency
- Actors for thread safety
- Property wrappers
- Result builders
- Protocols and generics
- Value types vs reference types
- Automatic Reference Counting (ARC)
- Macros (Swift 5.9+)

### SwiftUI

- Declarative UI framework
- State management
- View composition
- Layout system
- Animations
- Navigation

### Combine

- Reactive programming
- Publishers and subscribers
- Operators
- Error handling

## SwiftUI

### Basic Views

```swift
import SwiftUI

struct ContentView: View {
    @State private var name = ""
    @State private var count = 0

    var body: some View {
        VStack(spacing: 20) {
            Text("Hello, \(name.isEmpty ? "World" : name)!")
                .font(.title)
                .foregroundColor(.blue)

            TextField("Enter name", text: $name)
                .textFieldStyle(.roundedBorder)
                .padding()

            HStack {
                Button("Decrement") {
                    count -= 1
                }

                Text("\(count)")
                    .frame(minWidth: 50)

                Button("Increment") {
                    count += 1
                }
            }
            .buttonStyle(.borderedProminent)
        }
        .padding()
    }
}
```

### State Management

```swift
// @State - local view state
struct CounterView: View {
    @State private var count = 0

    var body: some View {
        Button("Count: \(count)") {
            count += 1
        }
    }
}

// @Binding - pass state reference
struct ChildView: View {
    @Binding var isOn: Bool

    var body: some View {
        Toggle("Setting", isOn: $isOn)
    }
}

// @ObservableObject - external state
class UserViewModel: ObservableObject {
    @Published var user: User?
    @Published var isLoading = false
    @Published var error: Error?

    func fetchUser() async {
        isLoading = true
        defer { isLoading = false }

        do {
            user = try await APIClient.shared.fetchUser()
        } catch {
            self.error = error
        }
    }
}

struct UserView: View {
    @StateObject private var viewModel = UserViewModel()

    var body: some View {
        Group {
            if viewModel.isLoading {
                ProgressView()
            } else if let user = viewModel.user {
                UserDetailView(user: user)
            } else if let error = viewModel.error {
                ErrorView(error: error)
            }
        }
        .task {
            await viewModel.fetchUser()
        }
    }
}

// @EnvironmentObject - app-wide state
@main
struct MyApp: App {
    @StateObject private var appState = AppState()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(appState)
        }
    }
}

struct SomeView: View {
    @EnvironmentObject var appState: AppState

    var body: some View {
        Text(appState.currentUser?.name ?? "Guest")
    }
}
```

### Lists and Navigation

```swift
struct PostListView: View {
    let posts: [Post]
    @State private var selectedPost: Post?

    var body: some View {
        NavigationStack {
            List(posts) { post in
                NavigationLink(value: post) {
                    VStack(alignment: .leading) {
                        Text(post.title)
                            .font(.headline)
                        Text(post.excerpt)
                            .font(.subheadline)
                            .foregroundColor(.secondary)
                    }
                }
            }
            .navigationTitle("Posts")
            .navigationDestination(for: Post.self) { post in
                PostDetailView(post: post)
            }
        }
    }
}
```

### Custom Modifiers

```swift
struct CardModifier: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .background(Color.white)
            .cornerRadius(10)
            .shadow(radius: 5)
    }
}

extension View {
    func cardStyle() -> some View {
        modifier(CardModifier())
    }
}

// Usage
Text("Hello")
    .cardStyle()
```

## Networking

```swift
actor APIClient {
    static let shared = APIClient()

    private let baseURL = URL(string: "https://api.example.com")!
    private let decoder: JSONDecoder = {
        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase
        return decoder
    }()

    func fetch<T: Decodable>(_ endpoint: String) async throws -> T {
        let url = baseURL.appendingPathComponent(endpoint)
        let (data, response) = try await URLSession.shared.data(from: url)

        guard let httpResponse = response as? HTTPURLResponse,
              (200...299).contains(httpResponse.statusCode) else {
            throw APIError.invalidResponse
        }

        return try decoder.decode(T.self, from: data)
    }

    func post<T: Encodable, R: Decodable>(
        _ endpoint: String,
        body: T
    ) async throws -> R {
        var request = URLRequest(url: baseURL.appendingPathComponent(endpoint))
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpBody = try JSONEncoder().encode(body)

        let (data, _) = try await URLSession.shared.data(for: request)
        return try decoder.decode(R.self, from: data)
    }
}

enum APIError: LocalizedError {
    case invalidResponse
    case decodingError

    var errorDescription: String? {
        switch self {
        case .invalidResponse:
            return "Invalid server response"
        case .decodingError:
            return "Failed to decode response"
        }
    }
}
```

## Combine Framework

```swift
import Combine

class SearchViewModel: ObservableObject {
    @Published var searchText = ""
    @Published var results: [SearchResult] = []
    @Published var isLoading = false

    private var cancellables = Set<AnyCancellable>()

    init() {
        $searchText
            .debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
            .removeDuplicates()
            .sink { [weak self] text in
                self?.performSearch(text)
            }
            .store(in: &cancellables)
    }

    private func performSearch(_ text: String) {
        guard !text.isEmpty else {
            results = []
            return
        }

        isLoading = true

        Task {
            do {
                let searchResults: [SearchResult] = try await APIClient.shared
                    .fetch("search?q=\(text)")
                await MainActor.run {
                    self.results = searchResults
                    self.isLoading = false
                }
            } catch {
                await MainActor.run {
                    self.isLoading = false
                }
            }
        }
    }
}
```

## Testing

```swift
import XCTest
@testable import MyApp

final class UserViewModelTests: XCTestCase {
    var viewModel: UserViewModel!
    var mockAPIClient: MockAPIClient!

    override func setUp() {
        super.setUp()
        mockAPIClient = MockAPIClient()
        viewModel = UserViewModel(apiClient: mockAPIClient)
    }

    func testFetchUserSuccess() async throws {
        // Given
        let expectedUser = User(id: UUID(), name: "Alice", email: "alice@example.com")
        mockAPIClient.userToReturn = expectedUser

        // When
        await viewModel.fetchUser()

        // Then
        XCTAssertEqual(viewModel.user?.name, "Alice")
        XCTAssertNil(viewModel.error)
        XCTAssertFalse(viewModel.isLoading)
    }

    func testFetchUserFailure() async {
        // Given
        mockAPIClient.shouldFail = true

        // When
        await viewModel.fetchUser()

        // Then
        XCTAssertNil(viewModel.user)
        XCTAssertNotNil(viewModel.error)
        XCTAssertFalse(viewModel.isLoading)
    }
}

// Mock
class MockAPIClient {
    var userToReturn: User?
    var shouldFail = false

    func fetchUser() async throws -> User {
        if shouldFail {
            throw APIError.invalidResponse
        }
        return userToReturn ?? User(id: UUID(), name: "Test", email: "test@example.com")
    }
}
```

## Best Practices

### Code Organization

- Use MVVM pattern for SwiftUI
- Separate business logic from views
- Use dependency injection
- Keep views small and composable

### Memory Management

- Understand ARC (Automatic Reference Counting)
- Use weak references for delegates
- Break retain cycles with [weak self] or [unowned self]
- Use actors for mutable shared state

### Performance

- Use lazy loading where appropriate
- Avoid unnecessary view updates
- Profile with Instruments
- Use value types (structs) by default

### Swift Concurrency

- Prefer async/await over completion handlers
- Use actors for thread-safe mutable state
- Use @MainActor for UI updates
- Avoid blocking the main thread

## Anti-Patterns to Avoid

❌ **Force unwrapping**: Use optional binding instead
❌ **Massive view controllers**: Extract logic to view models
❌ **Strong reference cycles**: Use weak/unowned references
❌ **Blocking main thread**: Use async/await
❌ **Ignoring memory warnings**: Handle memory pressure
❌ **Not using guard**: Use guard for early exits
❌ **Implicit unwrapping**: Prefer explicit optionals

## Reference Documentation

Detailed material lives alongside this skill and is read on demand:

- [Swift Syntax](references/SWIFT_SYNTAX.md) — Basics and Optionals, Functions and Closures, Structs and Classes, Enums, Async/Await (Swift 5.5+), Actors (Thread Safety)

## Resources

- Swift Documentation: https://swift.org/documentation/
- SwiftUI Tutorials: https://developer.apple.com/tutorials/swiftui
- WWDC Sessions: https://developer.apple.com/videos/
- Swift by Sundell: https://www.swiftbysundell.com/
- Hacking with Swift: https://www.hackingwithswift.com/

