All skills

Code generator skills that produce production-ready Swift code for common app components. Use when user wants to add logging, analytics, onboarding, review prompts, networking, authentication, paywalls, settings, persistence, error monitoring, CI/CD pipelines, localization, push notifications, deep linking, testing, accessibility, widgets, feature flags, app icons, image caching, pagination, HTTP caching, share cards, social export, subscription lifecycle, referral systems, watermarks, streak tracking, milestone celebrations, what's new screens, lapsed user re-engagement, usage insights, variable rewards, consent flows, account deletion, permission priming, force updates, state restoration, debug menus, offline queues, feedback forms, announcement banners, quick win sessions, Spotlight indexing, App Clips, screenshot automation, background processing, app extensions, data export, or SwiftUI preview sample data and variant matrices.

Use this Skill: https://skilld.dev/gh/rshankras/claude-code-apple-skills/generators

This session only. Nothing lands on disk.

networking-layernetworking-patterns.md

≈2.5k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Networking Patterns and Best Practices

Protocol-Based Architecture

Why Protocols?

  1. Testability - Use MockAPIClient in tests
  2. Flexibility - Swap implementations (URLSession, custom)
  3. Abstraction - Hide URLSession details from rest of app

Core Protocol

protocol APIClient: Sendable {
    func request<E: APIEndpoint>(_ endpoint: E) async throws -> E.Response
}

Endpoint Protocol

protocol APIEndpoint {
    associatedtype Response: Decodable

    var path: String { get }
    var method: HTTPMethod { get }
    var headers: [String: String] { get }
    var queryItems: [URLQueryItem]? { get }
    var body: Data? { get }
}

// Defaults
extension APIEndpoint {
    var method: HTTPMethod { .get }
    var headers: [String: String] { [:] }
    var queryItems: [URLQueryItem]? { nil }
    var body: Data? { nil }
}

Error Handling

Typed Errors

enum NetworkError: Error, LocalizedError {
    case invalidURL
    case noData
    case decodingFailed(Error)
    case httpError(statusCode: Int, data: Data?)
    case networkUnavailable
    case timeout
    case unauthorized
    case serverError(String)

    var errorDescription: String? {
        switch self {
        case .invalidURL:
            return "Invalid URL"
        case .noData:
            return "No data received"
        case .decodingFailed(let error):
            return "Failed to decode: \(error.localizedDescription)"
        case .httpError(let code, _):
            return "HTTP error: \(code)"
        case .networkUnavailable:
            return "Network unavailable"
        case .timeout:
            return "Request timed out"
        case .unauthorized:
            return "Unauthorized - please sign in again"
        case .serverError(let message):
            return "Server error: \(message)"
        }
    }
}

Error Mapping

func mapHTTPError(statusCode: Int, data: Data?) -> NetworkError {
    switch statusCode {
    case 401:
        return .unauthorized
    case 403:
        return .httpError(statusCode: statusCode, data: data)
    case 404:
        return .httpError(statusCode: statusCode, data: data)
    case 500...599:
        let message = data.flatMap { String(data: $0, encoding: .utf8) } ?? "Unknown"
        return .serverError(message)
    default:
        return .httpError(statusCode: statusCode, data: data)
    }
}

Authentication Patterns

Bearer Token

struct APIConfiguration {
    let baseURL: URL
    var authToken: String?

    func authorizationHeader() -> [String: String] {
        guard let token = authToken else { return [:] }
        return ["Authorization": "Bearer \(token)"]
    }
}

API Key

struct APIConfiguration {
    let baseURL: URL
    let apiKey: String
    let apiKeyLocation: APIKeyLocation

    enum APIKeyLocation {
        case header(name: String)
        case queryParam(name: String)
    }
}

Token Refresh

actor TokenManager {
    private var token: String?
    private var refreshTask: Task<String, Error>?

    func validToken() async throws -> String {
        if let token = token, !isExpired(token) {
            return token
        }

        // Avoid multiple simultaneous refresh calls
        if let refreshTask = refreshTask {
            return try await refreshTask.value
        }

        let task = Task {
            let newToken = try await refreshToken()
            self.token = newToken
            return newToken
        }

        refreshTask = task
        defer { refreshTask = nil }

        return try await task.value
    }
}

Retry Logic

Exponential Backoff

struct RetryPolicy {
    let maxRetries: Int
    let initialDelay: TimeInterval
    let multiplier: Double
    let retryableErrors: Set<Int>

    static let `default` = RetryPolicy(
        maxRetries: 3,
        initialDelay: 1.0,
        multiplier: 2.0,
        retryableErrors: [408, 429, 500, 502, 503, 504]
    )

    func delay(for attempt: Int) -> TimeInterval {
        initialDelay * pow(multiplier, Double(attempt))
    }

    func shouldRetry(statusCode: Int, attempt: Int) -> Bool {
        attempt < maxRetries && retryableErrors.contains(statusCode)
    }
}

Implementation

func requestWithRetry<E: APIEndpoint>(_ endpoint: E) async throws -> E.Response {
    var lastError: Error?

    for attempt in 0..<retryPolicy.maxRetries {
        do {
            return try await request(endpoint)
        } catch let error as NetworkError {
            lastError = error

            if case .httpError(let code, _) = error,
               retryPolicy.shouldRetry(statusCode: code, attempt: attempt) {
                let delay = retryPolicy.delay(for: attempt)
                try await Task.sleep(for: .seconds(delay))
                continue
            }

            throw error
        }
    }

    throw lastError ?? NetworkError.noData
}

Request/Response Logging

Debug Logging

struct NetworkLogger {
    static func logRequest(_ request: URLRequest) {
        #if DEBUG
        print("➡️ \(request.httpMethod ?? "?") \(request.url?.absoluteString ?? "")")
        if let headers = request.allHTTPHeaderFields {
            print("   Headers: \(headers)")
        }
        if let body = request.httpBody, let string = String(data: body, encoding: .utf8) {
            print("   Body: \(string)")
        }
        #endif
    }

    static func logResponse(_ response: HTTPURLResponse, data: Data?, duration: TimeInterval) {
        #if DEBUG
        let emoji = (200...299).contains(response.statusCode) ? "✅" : "❌"
        print("\(emoji) \(response.statusCode) (\(String(format: "%.2f", duration))s)")
        if let data = data, let string = String(data: data, encoding: .utf8) {
            print("   Response: \(string.prefix(500))")
        }
        #endif
    }
}

Swift 6.2 Concurrency Patterns

Background Processing with @concurrent

For CPU-intensive work like JSON parsing:

final class URLSessionAPIClient: APIClient {
    // Heavy parsing runs on background thread
    @concurrent
    private static func decode<T: Decodable>(_ data: Data) async throws -> T {
        try JSONDecoder().decode(T.self, from: data)
    }

    func request<E: APIEndpoint>(_ endpoint: E) async throws -> E.Response {
        let (data, response) = try await session.data(for: request)
        // ... validate response ...
        return try await Self.decode(data)
    }
}

MainActor Integration

@MainActor
class NetworkViewModel: ObservableObject {
    @Published var items: [Item] = []
    @Published var isLoading = false
    @Published var error: NetworkError?

    private let apiClient: APIClient

    func fetch() async {
        isLoading = true
        defer { isLoading = false }

        do {
            items = try await apiClient.request(ItemsEndpoint())
        } catch let error as NetworkError {
            self.error = error
        }
    }
}

Caching Patterns

URLCache Configuration

let cache = URLCache(
    memoryCapacity: 10 * 1024 * 1024,  // 10 MB memory
    diskCapacity: 50 * 1024 * 1024     // 50 MB disk
)

let configuration = URLSessionConfiguration.default
configuration.urlCache = cache
configuration.requestCachePolicy = .returnCacheDataElseLoad

Custom Caching

actor ResponseCache {
    private var cache: [String: (data: Data, expiry: Date)] = [:]

    func get<T: Decodable>(_ key: String) -> T? {
        guard let entry = cache[key], entry.expiry > Date() else {
            cache.removeValue(forKey: key)
            return nil
        }
        return try? JSONDecoder().decode(T.self, from: entry.data)
    }

    func set(_ key: String, data: Data, ttl: TimeInterval = 300) {
        cache[key] = (data, Date().addingTimeInterval(ttl))
    }
}

Testing Patterns

Mock Client

final class MockAPIClient: APIClient {
    private var responses: [String: Any] = [:]
    private var errors: [String: Error] = [:]

    func mockResponse<E: APIEndpoint>(for type: E.Type, response: E.Response) {
        responses[String(describing: type)] = response
    }

    func mockError<E: APIEndpoint>(for type: E.Type, error: Error) {
        errors[String(describing: type)] = error
    }

    func request<E: APIEndpoint>(_ endpoint: E) async throws -> E.Response {
        let key = String(describing: E.self)

        if let error = errors[key] {
            throw error
        }

        guard let response = responses[key] as? E.Response else {
            throw NetworkError.noData
        }

        return response
    }
}

Testing Example

@Test
func fetchUsersSuccess() async throws {
    let mockClient = MockAPIClient()
    mockClient.mockResponse(for: UsersEndpoint.self, response: [.mock])

    let viewModel = UsersViewModel(apiClient: mockClient)
    await viewModel.fetch()

    #expect(viewModel.users.count == 1)
    #expect(viewModel.error == nil)
}

@Test
func fetchUsersError() async throws {
    let mockClient = MockAPIClient()
    mockClient.mockError(for: UsersEndpoint.self, error: NetworkError.networkUnavailable)

    let viewModel = UsersViewModel(apiClient: mockClient)
    await viewModel.fetch()

    #expect(viewModel.users.isEmpty)
    #expect(viewModel.error == .networkUnavailable)
}

Anti-Patterns to Avoid

Don't Use Stringly-Typed APIs

// Bad
func fetch(path: String) async throws -> Data

// Good
func request<E: APIEndpoint>(_ endpoint: E) async throws -> E.Response

Don't Ignore Errors

// Bad
let data = try? await fetch()

// Good
do {
    let data = try await fetch()
} catch {
    handleError(error)
}

Don't Block Main Thread

// Bad - synchronous on main
let data = try Data(contentsOf: url)

// Good - async
let (data, _) = try await URLSession.shared.data(from: url)

Source: SKILL.md on GitHub

1 alert16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The 'generators' skill provides a robust set of templates and workflows for generating Swift code for iOS and macOS applications. Security analysis identified a surface for indirect prompt injection because the skill reads local project files to adapt its generation logic. It also utilizes dynamic execution by generating and running local Swift scripts to create application icons and uses shell commands to process images and audit project code. All external references are to well-known, established services.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: MEDIUM · 1 issue

  • Runlayer7mo

    59/179 files flagged

  • ZeroLeaks5mo

    Scan incomplete

Signed by skilld at 30e898f. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Steadyupdated 2 months ago
What it can do
Reads files Edits files Runs commands
last_verified
2026-07-16
review_by
2027-06-22
os_version
iOS 27 / macOS 27
All 7 allowed tools
ReadWriteEditGlobGrepBashAskUserQuestion

README badge

README badge for rshankras/claude-code-apple-skills/generators