swift-expert

Expert guidance for Swift development including iOS/macOS apps, SwiftUI, Combine, async/await, and Swift 5.9+ features.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "swift-expert" with this command: npx skills add personamanagmentlayer/pcl/personamanagmentlayer-pcl-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

Swift Syntax

Basics and Optionals

// Variables and constants var mutableValue = 42 let constantValue = 100

// Optionals var optionalName: String? = "Alice"

// Optional binding if let name = optionalName { print("Hello, (name)") }

// Optional chaining let length = optionalName?.count

// Nil coalescing let displayName = optionalName ?? "Unknown"

// Guard statement func greet(person: String?) { guard let name = person else { print("No name provided") return } print("Hello, (name)") }

Functions and Closures

// Function with labeled parameters func greet(person: String, from hometown: String) -> String { return "Hello (person) from (hometown)!" }

// Closures let numbers = [1, 2, 3, 4, 5] let doubled = numbers.map { $0 * 2 } let evens = numbers.filter { $0 % 2 == 0 } let sum = numbers.reduce(0, +)

// Trailing closure numbers.forEach { number in print(number) }

// Capture values func makeIncrementer(step: Int) -> () -> Int { var total = 0 return { total += step return total } }

Structs and Classes

// Struct (value type, preferred) struct User { let id: UUID var name: String var email: String

// Computed property
var displayName: String {
    name.isEmpty ? "Anonymous" : name
}

// Method
mutating func updateEmail(_ newEmail: String) {
    email = newEmail
}

}

// Class (reference type) class ViewController { var title: String? weak var delegate: ViewControllerDelegate?

init(title: String?) {
    self.title = title
}

deinit {
    print("Deallocated")
}

}

// Protocol protocol Identifiable { var id: UUID { get } }

extension User: Identifiable {}

Enums

// Simple enum enum Direction { case north, south, east, west }

// Associated values enum Result<Success, Failure: Error> { case success(Success) case failure(Failure) }

// Raw values enum StatusCode: Int { case ok = 200 case notFound = 404 case serverError = 500 }

// Pattern matching switch result { case .success(let value): print("Success: (value)") case .failure(let error): print("Error: (error)") }

Async/Await (Swift 5.5+)

// Async function func fetchUser(id: String) async throws -> User { let url = URL(string: "https://api.example.com/users/\(id)")! let (data, _) = try await URLSession.shared.data(from: url) return try JSONDecoder().decode(User.self, from: data) }

// Call async function Task { do { let user = try await fetchUser(id: "123") print(user.name) } catch { print("Error: (error)") } }

// Parallel execution with async let func loadUserData(id: String) async throws -> (User, [Post], [Comment]) { async let user = fetchUser(id: id) async let posts = fetchPosts(userId: id) async let comments = fetchComments(userId: id)

return try await (user, posts, comments)

}

// Task groups for dynamic parallelism func fetchMultipleUsers(ids: [String]) 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
}

}

Actors (Thread Safety)

actor BankAccount { private var balance: Double = 0

func deposit(amount: Double) {
    balance += amount
}

func withdraw(amount: Double) throws {
    guard balance >= amount else {
        throw BankError.insufficientFunds
    }
    balance -= amount
}

func getBalance() -> Double {
    balance
}

}

// Usage (all access is async and serialized) let account = BankAccount() Task { await account.deposit(amount: 100) let balance = await account.getBalance() print(balance) }

SwiftUI

Basic Views

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

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

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

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

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&#x3C;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&#x3C;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

import Combine

class SearchViewModel: ObservableObject { @Published var searchText = "" @Published var results: [SearchResult] = [] @Published var isLoading = false

private var cancellables = Set&#x3C;AnyCancellable>()

init() {
    $searchText
        .debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
        .removeDuplicates()
        .sink { [weak self] text in
            self?.performSearch(text)
        }
        .store(in: &#x26;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

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

Resources

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

python-expert

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

devops-expert

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

code-review-expert

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

typescript-expert

No summary provided by upstream source.

Repository SourceNeeds Review