Claude Code Plugins

Community-maintained marketplace

Feedback

lang-swift-dev

@aRustyDev/ai
0
0

Foundational Swift development patterns covering modern Swift syntax, SwiftUI, protocol-oriented programming, and Cocoa Touch frameworks. Use when writing Swift code, building iOS/macOS/watchOS/tvOS applications, working with SwiftUI or UIKit, understanding Swift concurrency, or needing guidance on Swift project structure.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name lang-swift-dev
description Foundational Swift development patterns covering modern Swift syntax, SwiftUI, protocol-oriented programming, and Cocoa Touch frameworks. Use when writing Swift code, building iOS/macOS/watchOS/tvOS applications, working with SwiftUI or UIKit, understanding Swift concurrency, or needing guidance on Swift project structure.

Swift Development Fundamentals

Foundational Swift patterns and modern language features for Apple platform development. This skill covers core Swift syntax, SwiftUI, UIKit integration, and protocol-oriented design patterns.

Overview

┌─────────────────────────────────────────────────────────────────┐
│                   Swift Development Ecosystem                   │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│                    ┌──────────────────┐                         │
│                    │  lang-swift-dev  │ ◄── You are here        │
│                    │   (foundation)   │                         │
│                    └────────┬─────────┘                         │
│                             │                                   │
│         ┌───────────────────┼───────────────────┐               │
│         │                   │                   │               │
│         ▼                   ▼                   ▼               │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐         │
│  │   SwiftUI   │    │    UIKit    │    │   Swift     │         │
│  │ Mastery     │    │  Advanced   │    │  Package    │         │
│  └─────────────┘    └─────────────┘    └─────────────┘         │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

This skill covers:

  • Core Swift syntax (optionals, closures, generics, protocols)
  • SwiftUI basics (views, state management, modifiers)
  • Protocol-oriented programming patterns
  • Swift concurrency (async/await, actors, tasks)
  • UIKit fundamentals and integration
  • Xcode project structure and organization

This skill does NOT cover:

  • Advanced SwiftUI architecture - see swiftui-patterns-advanced
  • Complex UIKit patterns - see uikit-advanced-patterns
  • Swift Package Manager publishing - see swift-package-dev
  • iOS app distribution and App Store submission
  • Combine framework (prefer Swift concurrency for new code)

Quick Reference

Task Pattern
Define optional var name: String?
Unwrap safely if let name = name { }
Guard unwrap guard let name = name else { return }
Define protocol protocol Named { var name: String { get } }
Conform to protocol extension MyType: Named { }
Async function func fetch() async throws -> Data
Call async let data = try await fetch()
SwiftUI view struct ContentView: View { var body: some View { } }
State variable @State private var count = 0

Core Swift Patterns

Optionals and Unwrapping

// Optional declaration
var username: String?
var age: Int? = nil

// Safe unwrapping with if let
if let username = username {
    print("Hello, \(username)")
} else {
    print("No username set")
}

// Guard for early exit
func greet(name: String?) {
    guard let name = name else {
        print("Name required")
        return
    }
    print("Hello, \(name)")
}

// Nil coalescing
let displayName = username ?? "Guest"

// Optional chaining
let uppercased = username?.uppercased()

// Force unwrap (use sparingly!)
let name = username!  // Crashes if nil

Closures

// Basic closure
let multiply = { (a: Int, b: Int) -> Int in
    return a * b
}

// Type inference
let add = { a, b in a + b }

// Trailing closure syntax
[1, 2, 3].map { number in
    number * 2
}

// Shorthand argument names
[1, 2, 3].map { $0 * 2 }

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

let incrementByTwo = makeIncrementer(amount: 2)
print(incrementByTwo())  // 2
print(incrementByTwo())  // 4

// Escaping closures (stored beyond function scope)
func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        completion(.success(Data()))
    }
}

Protocols and Protocol-Oriented Programming

// Define a protocol
protocol Drawable {
    func draw()
}

protocol Identifiable {
    var id: String { get }
}

// Conform to protocols
struct Circle: Drawable {
    func draw() {
        print("Drawing circle")
    }
}

// Protocol composition
struct User: Identifiable, Codable {
    let id: String
    let name: String
}

// Protocol extensions (add default implementations)
extension Drawable {
    func draw() {
        print("Default drawing")
    }

    func render() {
        print("Rendering...")
        draw()
    }
}

// Protocol with associated types
protocol Container {
    associatedtype Item
    var items: [Item] { get }
    mutating func add(_ item: Item)
}

struct IntStack: Container {
    typealias Item = Int
    var items: [Int] = []

    mutating func add(_ item: Int) {
        items.append(item)
    }
}

Generics

// Generic function
func swap<T>(_ a: inout T, _ b: inout T) {
    let temp = a
    a = b
    b = temp
}

// Generic type
struct Stack<Element> {
    private var items: [Element] = []

    mutating func push(_ item: Element) {
        items.append(item)
    }

    mutating func pop() -> Element? {
        return items.popLast()
    }
}

// Generic constraints
func findIndex<T: Equatable>(of value: T, in array: [T]) -> Int? {
    for (index, element) in array.enumerated() {
        if element == value {
            return index
        }
    }
    return nil
}

// Where clauses
func allItemsMatch<C1: Container, C2: Container>(
    _ container1: C1,
    _ container2: C2
) -> Bool where C1.Item == C2.Item, C1.Item: Equatable {
    guard container1.items.count == container2.items.count else {
        return false
    }
    return zip(container1.items, container2.items).allSatisfy { $0 == $1 }
}

Enums and Pattern Matching

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

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

// Enum with raw values
enum HTTPStatus: Int {
    case ok = 200
    case notFound = 404
    case serverError = 500
}

// Pattern matching
let result: Result<String, Error> = .success("Data")

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

// If case pattern matching
if case .success(let value) = result {
    print("Got value: \(value)")
}

// Recursive enums
indirect enum Expression {
    case number(Int)
    case addition(Expression, Expression)
    case multiplication(Expression, Expression)
}

Property Wrappers

// Built-in property wrappers
@State private var count = 0
@Published var username = ""
@Environment(\.colorScheme) var colorScheme

// Custom property wrapper
@propertyWrapper
struct Clamped<Value: Comparable> {
    private var value: Value
    private let range: ClosedRange<Value>

    var wrappedValue: Value {
        get { value }
        set { value = min(max(newValue, range.lowerBound), range.upperBound) }
    }

    init(wrappedValue: Value, _ range: ClosedRange<Value>) {
        self.range = range
        self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
    }
}

// Usage
struct Game {
    @Clamped(0...100) var health = 100
}

var game = Game()
game.health = 150  // Clamped to 100
game.health = -10  // Clamped to 0

Swift Concurrency

Async/Await

// Define 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("Fetched user: \(user.name)")
    } catch {
        print("Error: \(error)")
    }
}

// Async let (parallel execution)
func loadData() async throws -> (User, [Post]) {
    async let user = fetchUser(id: "123")
    async let posts = fetchPosts(userId: "123")
    return try await (user, posts)
}

// Async sequences
func processLines(url: URL) async throws {
    for try await line in url.lines {
        print("Line: \(line)")
    }
}

Actors

// Actor for thread-safe state
actor BankAccount {
    private var balance: Double = 0

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

    func withdraw(amount: Double) -> Bool {
        guard balance >= amount else {
            return false
        }
        balance -= amount
        return true
    }

    func getBalance() -> Double {
        return balance
    }
}

// Usage (automatically synchronized)
let account = BankAccount()

Task {
    await account.deposit(amount: 100)
    let balance = await account.getBalance()
    print("Balance: \(balance)")
}

Tasks and Task Groups

// Detached task
Task.detached {
    await performBackgroundWork()
}

// Task group (structured concurrency)
func fetchAllUsers() async throws -> [User] {
    try await withThrowingTaskGroup(of: User.self) { group in
        for id in 1...10 {
            group.addTask {
                try await fetchUser(id: String(id))
            }
        }

        var users: [User] = []
        for try await user in group {
            users.append(user)
        }
        return users
    }
}

// Task cancellation
let task = Task {
    for i in 1...100 {
        if Task.isCancelled {
            print("Task cancelled at \(i)")
            return
        }
        await doWork(i)
    }
}

// Cancel after delay
Task {
    try await Task.sleep(nanoseconds: 1_000_000_000)
    task.cancel()
}

SwiftUI Fundamentals

View Basics

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack(spacing: 20) {
            Text("Hello, World!")
                .font(.largeTitle)
                .foregroundColor(.blue)

            Image(systemName: "star.fill")
                .font(.system(size: 50))

            Button("Tap Me") {
                print("Button tapped")
            }
        }
        .padding()
    }
}

State Management

struct CounterView: View {
    // State for view-local data
    @State private var count = 0

    var body: some View {
        VStack {
            Text("Count: \(count)")
                .font(.largeTitle)

            Button("Increment") {
                count += 1
            }
        }
    }
}

// ObservableObject for shared state
class UserViewModel: ObservableObject {
    @Published var username = ""
    @Published var isLoggedIn = false

    func login() {
        // Perform login
        isLoggedIn = true
    }
}

struct LoginView: View {
    @StateObject private var viewModel = UserViewModel()

    var body: some View {
        VStack {
            TextField("Username", text: $viewModel.username)
                .textFieldStyle(.roundedBorder)

            Button("Login") {
                viewModel.login()
            }

            if viewModel.isLoggedIn {
                Text("Welcome, \(viewModel.username)!")
            }
        }
        .padding()
    }
}

// Environment values
struct ThemedView: View {
    @Environment(\.colorScheme) var colorScheme

    var body: some View {
        Text("Theme: \(colorScheme == .dark ? "Dark" : "Light")")
    }
}

Lists and Navigation

struct Item: Identifiable {
    let id = UUID()
    let title: String
}

struct ItemListView: View {
    let items = [
        Item(title: "First"),
        Item(title: "Second"),
        Item(title: "Third")
    ]

    var body: some View {
        NavigationView {
            List(items) { item in
                NavigationLink(destination: DetailView(item: item)) {
                    Text(item.title)
                }
            }
            .navigationTitle("Items")
        }
    }
}

struct DetailView: View {
    let item: Item

    var body: some View {
        Text("Detail for \(item.title)")
            .navigationTitle(item.title)
    }
}

Custom Modifiers and ViewBuilder

// Custom view modifier
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())
    }
}

// ViewBuilder pattern
@ViewBuilder
func conditionalView(showText: Bool) -> some View {
    if showText {
        Text("Visible")
    } else {
        Image(systemName: "eye.slash")
    }
}

UIKit Integration

UIKit in SwiftUI (UIViewRepresentable)

import UIKit
import SwiftUI

struct TextView: UIViewRepresentable {
    @Binding var text: String

    func makeUIView(context: Context) -> UITextView {
        let textView = UITextView()
        textView.delegate = context.coordinator
        return textView
    }

    func updateUIView(_ uiView: UITextView, context: Context) {
        uiView.text = text
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(text: $text)
    }

    class Coordinator: NSObject, UITextViewDelegate {
        @Binding var text: String

        init(text: Binding<String>) {
            _text = text
        }

        func textViewDidChange(_ textView: UITextView) {
            text = textView.text
        }
    }
}

SwiftUI in UIKit (UIHostingController)

import UIKit
import SwiftUI

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        // Embed SwiftUI view in UIKit
        let swiftUIView = ContentView()
        let hostingController = UIHostingController(rootView: swiftUIView)

        addChild(hostingController)
        view.addSubview(hostingController.view)
        hostingController.view.frame = view.bounds
        hostingController.didMove(toParent: self)
    }
}

Basic UIKit Patterns

// UIViewController lifecycle
class MyViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // Setup after view loads
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        // Before view appears
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        // After view appears
    }
}

// Delegation pattern
protocol DataSourceDelegate: AnyObject {
    func didReceiveData(_ data: String)
}

class DataSource {
    weak var delegate: DataSourceDelegate?

    func fetchData() {
        // Fetch data
        delegate?.didReceiveData("Data")
    }
}

Project Structure

Swift Package Structure

MyPackage/
├── Package.swift
├── Sources/
│   └── MyPackage/
│       ├── MyPackage.swift
│       └── Models/
│           └── User.swift
├── Tests/
│   └── MyPackageTests/
│       └── MyPackageTests.swift
└── README.md

iOS App Structure

MyApp/
├── MyApp/
│   ├── App/
│   │   ├── MyAppApp.swift
│   │   └── ContentView.swift
│   ├── Models/
│   │   └── User.swift
│   ├── Views/
│   │   ├── HomeView.swift
│   │   └── DetailView.swift
│   ├── ViewModels/
│   │   └── UserViewModel.swift
│   ├── Services/
│   │   └── NetworkService.swift
│   ├── Resources/
│   │   └── Assets.xcassets
│   └── Supporting Files/
│       └── Info.plist
└── MyAppTests/
    └── MyAppTests.swift

Package.swift Example

// swift-tools-version: 5.9
import PackageDescription

let package = Package(
    name: "MyPackage",
    platforms: [
        .iOS(.v16),
        .macOS(.v13)
    ],
    products: [
        .library(
            name: "MyPackage",
            targets: ["MyPackage"]
        ),
    ],
    dependencies: [
        .package(url: "https://github.com/example/package.git", from: "1.0.0"),
    ],
    targets: [
        .target(
            name: "MyPackage",
            dependencies: []
        ),
        .testTarget(
            name: "MyPackageTests",
            dependencies: ["MyPackage"]
        ),
    ]
)

Common Idioms

Result Builders

@resultBuilder
struct StringBuilder {
    static func buildBlock(_ components: String...) -> String {
        components.joined(separator: " ")
    }
}

@StringBuilder
func makeGreeting() -> String {
    "Hello"
    "World"
    "from"
    "Swift"
}

print(makeGreeting())  // "Hello World from Swift"

Codable for JSON

struct User: Codable {
    let id: Int
    let name: String
    let email: String

    // Custom coding keys
    enum CodingKeys: String, CodingKey {
        case id
        case name
        case email = "email_address"
    }
}

// Decode JSON
let json = """
{
    "id": 1,
    "name": "John",
    "email_address": "john@example.com"
}
""".data(using: .utf8)!

let user = try JSONDecoder().decode(User.self, from: json)

// Encode to JSON
let data = try JSONEncoder().encode(user)

Error Handling

enum NetworkError: Error {
    case invalidURL
    case noData
    case decodingFailed
}

func fetchData(from urlString: String) throws -> Data {
    guard let url = URL(string: urlString) else {
        throw NetworkError.invalidURL
    }

    // Fetch data...
    guard let data = try? Data(contentsOf: url) else {
        throw NetworkError.noData
    }

    return data
}

// Using do-catch
do {
    let data = try fetchData(from: "https://api.example.com")
    print("Fetched \(data.count) bytes")
} catch NetworkError.invalidURL {
    print("Invalid URL")
} catch {
    print("Error: \(error)")
}

Troubleshooting

Optional Unwrapping Crashes

Problem: Fatal error: Unexpectedly found nil while unwrapping an Optional value

// Bad: Force unwrapping
let name = user.name!  // Crashes if nil

// Good: Safe unwrapping
if let name = user.name {
    print(name)
}

// Or: Guard
guard let name = user.name else {
    return
}

Retain Cycles in Closures

Problem: Memory leaks from strong reference cycles

// Bad: Strong reference to self
class ViewController {
    var completion: (() -> Void)?

    func setup() {
        completion = {
            self.doSomething()  // Strong reference cycle
        }
    }
}

// Good: Weak or unowned self
class ViewController {
    var completion: (() -> Void)?

    func setup() {
        completion = { [weak self] in
            self?.doSomething()
        }
    }
}

SwiftUI View Not Updating

Problem: View doesn't update when data changes

// Bad: No property wrapper
class ViewModel {
    var count = 0  // Changes won't trigger updates
}

// Good: Use @Published in ObservableObject
class ViewModel: ObservableObject {
    @Published var count = 0
}

struct ContentView: View {
    @StateObject var viewModel = ViewModel()

    var body: some View {
        Text("Count: \(viewModel.count)")
    }
}

Actor Reentrancy Issues

Problem: Unexpected state changes in async actor methods

actor Counter {
    private var value = 0

    // Potential reentrancy issue
    func increment() async {
        await Task.sleep(1_000_000_000)
        value += 1  // Value might have changed during await
    }

    // Better: Check state after await
    func safeIncrement() async -> Int {
        let currentValue = value
        await Task.sleep(1_000_000_000)
        value = currentValue + 1
        return value
    }
}

Testing

XCTest Basics

import XCTest
@testable import MyApp

final class UserTests: XCTestCase {
    var sut: User!  // System Under Test

    override func setUp() {
        super.setUp()
        sut = User(name: "Alice", age: 30)
    }

    override func tearDown() {
        sut = nil
        super.tearDown()
    }

    func testUserInitialization() {
        // Arrange & Act (done in setUp)

        // Assert
        XCTAssertEqual(sut.name, "Alice")
        XCTAssertEqual(sut.age, 30)
    }

    func testUserValidation() {
        // Arrange
        let invalidUser = User(name: "", age: -1)

        // Act
        let isValid = invalidUser.validate()

        // Assert
        XCTAssertFalse(isValid)
    }
}

Swift Testing (@Test Macro)

import Testing
@testable import MyApp

struct UserTests {
    // Simple test
    @Test func userInitialization() {
        let user = User(name: "Alice", age: 30)
        #expect(user.name == "Alice")
        #expect(user.age == 30)
    }

    // Test with custom name
    @Test("User validation rejects empty names")
    func userValidation() {
        let user = User(name: "", age: 30)
        #expect(!user.validate())
    }

    // Parameterized tests
    @Test("Age validation", arguments: [
        (18, true),
        (17, false),
        (65, true),
        (0, false),
        (-1, false)
    ])
    func ageValidation(age: Int, expected: Bool) {
        let user = User(name: "Test", age: age)
        #expect(user.isValidAge() == expected)
    }

    // Tests with tags
    @Test(.tags(.critical))
    func criticalFeature() {
        let result = performCriticalOperation()
        #expect(result != nil)
    }
}

// Custom tags
extension Tag {
    @Tag static var critical: Self
    @Tag static var integration: Self
    @Tag static var performance: Self
}

XCTest Assertions

// Boolean assertions
XCTAssertTrue(condition)
XCTAssertFalse(condition)

// Nil assertions
XCTAssertNil(value)
XCTAssertNotNil(value)

// Equality assertions
XCTAssertEqual(actual, expected)
XCTAssertNotEqual(actual, unexpected)
XCTAssertIdentical(object1, object2)  // Same instance

// Numeric comparisons
XCTAssertGreaterThan(5, 3)
XCTAssertLessThan(3, 5)
XCTAssertGreaterThanOrEqual(5, 5)
XCTAssertLessThanOrEqual(3, 5)

// Floating point equality with accuracy
XCTAssertEqual(3.14, 3.14159, accuracy: 0.01)

// Error assertions
XCTAssertThrowsError(try riskyOperation()) { error in
    XCTAssertEqual(error as? MyError, MyError.notFound)
}
XCTAssertNoThrow(try safeOperation())

// Custom failure
XCTFail("Test failed: \(reason)")

Async Testing

// Async/await testing
func testAsyncFetch() async throws {
    // Act
    let user = try await networkService.fetchUser(id: "123")

    // Assert
    XCTAssertEqual(user.id, "123")
    XCTAssertNotNil(user.name)
}

// Testing with expectations
func testCompletionHandler() {
    let expectation = expectation(description: "Data fetched")

    networkService.fetchUser(id: "123") { result in
        switch result {
        case .success(let user):
            XCTAssertEqual(user.id, "123")
            expectation.fulfill()
        case .failure(let error):
            XCTFail("Failed with error: \(error)")
        }
    }

    waitForExpectations(timeout: 5.0)
}

// Multiple expectations
func testMultipleAsync() async throws {
    async let user = fetchUser(id: "123")
    async let posts = fetchPosts(userId: "123")

    let (fetchedUser, fetchedPosts) = try await (user, posts)

    XCTAssertEqual(fetchedUser.id, "123")
    XCTAssertFalse(fetchedPosts.isEmpty)
}

// Swift Testing async
@Test func asyncFetch() async throws {
    let user = try await networkService.fetchUser(id: "123")
    #expect(user.id == "123")
}

// Testing actors
func testActorIsolation() async {
    let counter = Counter()

    await counter.increment()
    await counter.increment()

    let value = await counter.value
    XCTAssertEqual(value, 2)
}

Mocking Protocols

// Define protocol
protocol NetworkService {
    func fetchUser(id: String) async throws -> User
    func updateUser(_ user: User) async throws
}

// Mock implementation
class MockNetworkService: NetworkService {
    var fetchUserCalled = false
    var fetchUserCallCount = 0
    var userToReturn: User?
    var errorToThrow: Error?

    func fetchUser(id: String) async throws -> User {
        fetchUserCalled = true
        fetchUserCallCount += 1

        if let error = errorToThrow {
            throw error
        }

        return userToReturn ?? User(id: id, name: "Mock User", age: 30)
    }

    var updateUserCalled = false
    var updatedUser: User?

    func updateUser(_ user: User) async throws {
        updateUserCalled = true
        updatedUser = user

        if let error = errorToThrow {
            throw error
        }
    }
}

// Using mock in tests
func testUserViewModel() async throws {
    // Arrange
    let mockService = MockNetworkService()
    mockService.userToReturn = User(id: "123", name: "Alice", age: 30)
    let viewModel = UserViewModel(service: mockService)

    // Act
    await viewModel.loadUser(id: "123")

    // Assert
    XCTAssertTrue(mockService.fetchUserCalled)
    XCTAssertEqual(mockService.fetchUserCallCount, 1)
    XCTAssertEqual(viewModel.user?.name, "Alice")
}

Spy Pattern

// Spy records all calls and arguments
class SpyNetworkService: NetworkService {
    var calls: [(method: String, arguments: Any)] = []

    func fetchUser(id: String) async throws -> User {
        calls.append((method: "fetchUser", arguments: id))
        return User(id: id, name: "Spy User", age: 30)
    }

    func updateUser(_ user: User) async throws {
        calls.append((method: "updateUser", arguments: user))
    }
}

// Using spy
func testCallSequence() async throws {
    let spy = SpyNetworkService()
    let viewModel = UserViewModel(service: spy)

    await viewModel.loadUser(id: "123")
    await viewModel.updateUserName("Bob")

    XCTAssertEqual(spy.calls.count, 2)
    XCTAssertEqual(spy.calls[0].method, "fetchUser")
    XCTAssertEqual(spy.calls[1].method, "updateUser")
}

Stub Pattern

// Stub returns predetermined responses
class StubNetworkService: NetworkService {
    var stubbedUsers: [String: User] = [:]

    func fetchUser(id: String) async throws -> User {
        guard let user = stubbedUsers[id] else {
            throw NetworkError.notFound
        }
        return user
    }

    func updateUser(_ user: User) async throws {
        // No-op for stub
    }
}

// Using stub
func testWithPresetData() async throws {
    let stub = StubNetworkService()
    stub.stubbedUsers = [
        "123": User(id: "123", name: "Alice", age: 30),
        "456": User(id: "456", name: "Bob", age: 25)
    ]

    let user = try await stub.fetchUser(id: "123")
    XCTAssertEqual(user.name, "Alice")
}

Testing SwiftUI Views

import ViewInspector

struct ContentViewTests: XCTestCase {
    func testButtonTap() throws {
        var viewModel = ViewModel()
        let view = ContentView(viewModel: viewModel)

        // Find button and simulate tap
        let button = try view.inspect().find(button: "Increment")
        try button.tap()

        // Verify state changed
        XCTAssertEqual(viewModel.count, 1)
    }

    func testTextDisplayed() throws {
        let view = ContentView(title: "Hello")
        let text = try view.inspect().find(text: "Hello")
        XCTAssertNotNil(text)
    }
}

// Snapshot testing (with SnapshotTesting library)
func testViewSnapshot() {
    let view = ContentView()
    assertSnapshot(matching: view, as: .image)
}

UI Testing Basics

import XCTest

final class AppUITests: XCTestCase {
    var app: XCUIApplication!

    override func setUp() {
        super.setUp()
        continueAfterFailure = false
        app = XCUIApplication()
        app.launch()
    }

    func testLoginFlow() {
        // Find elements
        let usernameField = app.textFields["Username"]
        let passwordField = app.secureTextFields["Password"]
        let loginButton = app.buttons["Login"]

        // Interact with UI
        usernameField.tap()
        usernameField.typeText("alice@example.com")

        passwordField.tap()
        passwordField.typeText("password123")

        loginButton.tap()

        // Verify navigation
        let welcomeText = app.staticTexts["Welcome, Alice!"]
        XCTAssertTrue(welcomeText.waitForExistence(timeout: 5))
    }

    func testSwipeToDelete() {
        let firstCell = app.cells.firstMatch
        XCTAssertTrue(firstCell.waitForExistence(timeout: 2))

        firstCell.swipeLeft()

        let deleteButton = firstCell.buttons["Delete"]
        deleteButton.tap()

        XCTAssertFalse(firstCell.exists)
    }
}

Performance Testing

func testPerformance() {
    measure {
        // Code to measure
        _ = heavyComputation()
    }
}

// Metrics-based performance testing
func testPerformanceMetrics() {
    let options = XCTMeasureOptions()
    options.iterationCount = 10

    measure(metrics: [XCTClockMetric(), XCTMemoryMetric()], options: options) {
        processLargeDataset()
    }
}

// Swift Testing performance
@Test(.timeLimit(.minutes(1)))
func performanceSensitiveOperation() {
    let result = expensiveComputation()
    #expect(result != nil)
}

Test Organization

// Group related tests
class UserValidationTests: XCTestCase {
    func testEmailValidation() { }
    func testPasswordValidation() { }
    func testAgeValidation() { }
}

class UserPersistenceTests: XCTestCase {
    func testSaveUser() { }
    func testLoadUser() { }
    func testDeleteUser() { }
}

// Swift Testing suites
@Suite("User Management")
struct UserManagementTests {
    @Suite("Validation")
    struct ValidationTests {
        @Test func emailValidation() { }
        @Test func passwordValidation() { }
    }

    @Suite("Persistence")
    struct PersistenceTests {
        @Test func saveUser() { }
        @Test func loadUser() { }
    }
}

Test Helpers

// Custom assertions
func assertUserValid(_ user: User, file: StaticString = #file, line: UInt = #line) {
    XCTAssertFalse(user.name.isEmpty, "User name is empty", file: file, line: line)
    XCTAssertGreaterThan(user.age, 0, "User age is invalid", file: file, line: line)
    XCTAssertNotNil(user.email, "User email is nil", file: file, line: line)
}

// Test fixtures
extension User {
    static func fixture(
        name: String = "Test User",
        age: Int = 30,
        email: String = "test@example.com"
    ) -> User {
        User(name: name, age: age, email: email)
    }
}

// Usage
func testUserFeature() {
    let user = User.fixture(name: "Alice")
    assertUserValid(user)
}

Cross-Cutting Patterns

For cross-language comparison and translation patterns, see:

  • patterns-concurrency-dev - async/await, actors, task groups
  • patterns-serialization-dev - Codable, JSON, property lists
  • patterns-metaprogramming-dev - Reflection, type introspection

References