CtrlK
BlogDocsLog inGet started
Tessl Logo

dpearson2699/swift-ios-skills

Agent skills for iOS, iPadOS, Swift, SwiftUI, and modern Apple framework development.

80

Quality

100%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Advisory

Suggest reviewing before use

Overview
Quality
Evals
Security
Files

lightweight-clients.mdskills/ios-networking/references/

Lightweight Clients (Closure-Based)

Use this pattern to keep networking or service dependencies simple and testable without introducing a full view model or heavy DI framework. It works well for SwiftUI apps where you want a small, composable API surface that can be swapped in previews/tests.

Intent

  • Provide a tiny "client" type made of async closures.
  • Keep business logic in a store or feature layer, not the view.
  • Enable easy stubbing in previews/tests.

Minimal shape

struct SomeClient {
    var fetchItems: (_ limit: Int) async throws -> [Item]
    var search: (_ query: String, _ limit: Int) async throws -> [Item]
}

extension SomeClient {
    static func live(baseURL: URL = URL(string: "https://example.com")!) -> SomeClient {
        let session = URLSession.shared  // Prototyping only. For production, create a URLSession with timeoutIntervalForRequest: 30, timeoutIntervalForResource: 300, waitsForConnectivity: true, and a URLCache.
        return SomeClient(
            fetchItems: { limit in
                // build URL, call session, decode
            },
            search: { query, limit in
                // build URL, call session, decode
            }
        )
    }
}

Usage pattern

@MainActor
@Observable final class ItemsStore {
    enum LoadState { case idle, loading, loaded, failed(String) }

    var items: [Item] = []
    var state: LoadState = .idle
    private let client: SomeClient

    init(client: SomeClient) {
        self.client = client
    }

    func load(limit: Int = 20) async {
        state = .loading
        do {
            items = try await client.fetchItems(limit)
            state = .loaded
        } catch {
            state = .failed(error.localizedDescription)
        }
    }
}
struct ContentView: View {
    @Environment(ItemsStore.self) private var store

    var body: some View {
        List(store.items) { item in
            Text(item.title)
        }
        .task { await store.load() }
    }
}
@main
struct MyApp: App {
    @State private var store = ItemsStore(client: .live())

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(store)
        }
    }
}

Guidance

  • Keep decoding and URL-building in the client; keep state changes in the store.
  • Make the store accept the client in init and keep it private.
  • Avoid global singletons; use .environment for store injection.
  • If you need multiple variants (mock/stub), add static func mock(...).

Pitfalls

  • Don't put UI state in the client; keep state in the store.
  • Don't capture self or view state in the client closures.

skills

.mcp.json

README.md

tile.json