# Swift Networking

> When to activate: URLSession, async networking, REST API clients, JSON decoding, multipart uploads, WebSocket in Swift

- Skill: `mattakushi432/swift-networking` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mattakushi432/swift-networking`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mattakushi432/swift-networking/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: Mattakushi432 (https://skillmd.com/u/mattakushi432)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mattakushi432/swift-networking

---


# Swift Networking

## URLSession with async/await

```swift
struct APIClient {
    let baseURL: URL
    let session: URLSession

    init(baseURL: URL, session: URLSession = .shared) {
        self.baseURL = baseURL
        self.session = session
    }

    func get<T: Decodable>(_ path: String, as type: T.Type = T.self) async throws -> T {
        let url = baseURL.appending(path: path)
        var request = URLRequest(url: url)
        request.setValue("application/json", forHTTPHeaderField: "Accept")

        let (data, response) = try await session.data(for: request)
        try validate(response)
        return try JSONDecoder.iso8601.decode(T.self, from: data)
    }

    func post<Body: Encodable, Response: Decodable>(_ path: String, body: Body) async throws -> Response {
        let url = baseURL.appending(path: path)
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpBody = try JSONEncoder().encode(body)

        let (data, response) = try await session.data(for: request)
        try validate(response)
        return try JSONDecoder.iso8601.decode(Response.self, from: data)
    }

    private func validate(_ response: URLResponse) throws {
        guard let http = response as? HTTPURLResponse else { throw APIError.invalidResponse }
        guard (200..<300).contains(http.statusCode) else {
            throw APIError.httpError(statusCode: http.statusCode)
        }
    }
}

enum APIError: LocalizedError {
    case invalidResponse
    case httpError(statusCode: Int)
    var errorDescription: String? {
        switch self {
        case .invalidResponse:      return "Invalid server response"
        case .httpError(let code):  return "HTTP error \(code)"
        }
    }
}

extension JSONDecoder {
    static let iso8601: JSONDecoder = {
        let d = JSONDecoder()
        d.dateDecodingStrategy = .iso8601
        return d
    }()
}
```

## Codable / JSON Decoding

```swift
struct Article: Codable {
    let id: Int
    let title: String
    let publishedAt: Date
    let author: Author

    // Custom key mapping
    enum CodingKeys: String, CodingKey {
        case id, title, author
        case publishedAt = "published_at"
    }
}

struct Author: Codable {
    let id: Int
    let name: String
    let avatarURL: URL

    enum CodingKeys: String, CodingKey {
        case id, name
        case avatarURL = "avatar_url"
    }
}
```

## Retry with Backoff

```swift
func withRetry<T>(
    maxAttempts: Int = 3,
    initialDelay: Duration = .seconds(1),
    operation: () async throws -> T
) async throws -> T {
    var delay = initialDelay
    for attempt in 1...maxAttempts {
        do {
            return try await operation()
        } catch where attempt < maxAttempts {
            try await Task.sleep(for: delay)
            delay = delay * 2  // exponential backoff
        }
    }
    return try await operation()  // final attempt propagates error
}
```

## Multipart Upload

```swift
func upload(image: Data, filename: String) async throws -> UploadResponse {
    let boundary = UUID().uuidString
    var request = URLRequest(url: uploadURL)
    request.httpMethod = "POST"
    request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")

    var body = Data()
    body.append("--\(boundary)\r\n".data(using: .utf8)!)
    body.append("Content-Disposition: form-data; name=\"file\"; filename=\"\(filename)\"\r\n".data(using: .utf8)!)
    body.append("Content-Type: image/jpeg\r\n\r\n".data(using: .utf8)!)
    body.append(image)
    body.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)

    request.httpBody = body
    let (data, _) = try await URLSession.shared.data(for: request)
    return try JSONDecoder().decode(UploadResponse.self, from: data)
}
```

## WebSocket

```swift
actor WebSocketManager {
    private var task: URLSessionWebSocketTask?

    func connect(url: URL) {
        task = URLSession.shared.webSocketTask(with: url)
        task?.resume()
        receiveLoop()
    }

    func send(_ text: String) async throws {
        try await task?.send(.string(text))
    }

    private func receiveLoop() {
        Task {
            while let task {
                do {
                    let message = try await task.receive()
                    switch message {
                    case .string(let text):  handleText(text)
                    case .data(let data):    handleData(data)
                    @unknown default:        break
                    }
                } catch {
                    break
                }
            }
        }
    }
}
```

## Common Anti-Patterns

- **Synchronous network calls** — always use async/await; never block the main thread
- **Force decoding** — let `JSONDecoder` throw; don't catch and return `nil`
- **Ignoring HTTP status codes** — validate response before decoding body
- **Hardcoded base URLs** — inject via configuration or environment
- **No retry on transient failures** — implement exponential backoff for 5xx/timeout errors

