| name | swift-networking |
| description | Handle networking in Swift - URLSession, async/await, Codable, API clients, error handling |
| version | 2.0.0 |
| sasmp_version | 1.3.0 |
| bonded_agent | 04-swift-data |
| bond_type | PRIMARY_BOND |
Swift Networking Skill
Modern networking patterns for Swift applications using URLSession, async/await, and type-safe API clients.
Prerequisites
- iOS 15+ / macOS 12+ (for async/await)
- Understanding of HTTP fundamentals
- Familiarity with Codable protocol
Parameters
parameters:
base_url:
type: string
required: true
description: API base URL
timeout:
type: number
default: 30
description: Request timeout in seconds
retry_enabled:
type: boolean
default: true
max_retries:
type: number
default: 3
auth_type:
type: string
enum: [none, bearer, api_key, basic]
default: bearer
Topics Covered
URLSession Configuration
| Configuration | Use Case |
|---|
.default | Standard caching, credentials |
.ephemeral | No persistent storage |
.background | Large transfers, app suspended |
HTTP Methods
| Method | Purpose | Body |
|---|
| GET | Retrieve resource | No |
| POST | Create resource | Yes |
| PUT | Replace resource | Yes |
| PATCH | Partial update | Yes |
| DELETE | Remove resource | Optional |
Response Handling
| Status Code | Meaning | Action |
|---|
| 200-299 | Success | Parse response |
| 400 | Bad Request | Validation error |
| 401 | Unauthorized | Refresh token / re-auth |
| 403 | Forbidden | Permission denied |
| 404 | Not Found | Resource missing |
| 429 | Rate Limited | Backoff and retry |
| 500-599 | Server Error | Retry with backoff |
Code Examples
Type-Safe API Client
import Foundation
protocol APIEndpoint {
associatedtype Response: Decodable
var path: String { get }
var method: HTTPMethod { get }
var headers: [String: String] { get }
var queryItems: [URLQueryItem]? { get }
var body: Encodable? { get }
}
extension APIEndpoint {
var headers: [String: String] { [:] }
var queryItems: [URLQueryItem]? { nil }
var body: Encodable? { nil }
}
enum HTTPMethod: String {
case get = "GET"
case post = "POST"
case put = "PUT"
case patch = "PATCH"
case delete =
}
{
session:
baseURL:
decoder:
encoder:
authToken: ?
(: , : .default) {
.baseURL baseURL
.session (configuration: configuration)
.decoder ()
.decoder.dateDecodingStrategy .iso8601
.decoder.keyDecodingStrategy .convertFromSnakeCase
.encoder ()
.encoder.dateEncodingStrategy .iso8601
.encoder.keyEncodingStrategy .convertToSnakeCase
}
( : ?) {
.authToken token
}
<: >( : ) -> . {
request buildRequest(for: endpoint)
(data, response) session.data(for: request)
validateResponse(response, data: data)
{
decoder.decode(.., from: data)
} {
.decodingError(error, data: data)
}
}
<: >( : ) -> {
components (url: baseURL.appendingPathComponent(endpoint.path), resolvingAgainstBaseURL: )
components.queryItems endpoint.queryItems
url components.url {
.invalidURL
}
request (url: url)
request.httpMethod endpoint.method.rawValue
request.setValue(, forHTTPHeaderField: )
request.setValue(, forHTTPHeaderField: )
token authToken {
request.setValue(, forHTTPHeaderField: )
}
endpoint.headers.forEach { request.setValue(, forHTTPHeaderField: ) }
body endpoint.body {
request.httpBody encoder.encode((body))
}
request
}
( : , : ) {
httpResponse response {
.invalidResponse
}
httpResponse.statusCode {
:
:
.unauthorized
:
.forbidden
:
.notFound
:
.rateLimited
:
.clientError(statusCode: httpResponse.statusCode, data: data)
:
.serverError(statusCode: httpResponse.statusCode)
:
.unknown(statusCode: httpResponse.statusCode)
}
}
}
: {
invalidURL
invalidResponse
unauthorized
forbidden
notFound
rateLimited
clientError(statusCode: , data: )
serverError(statusCode: )
decodingError(, data: )
unknown(statusCode: )
errorDescription: ? {
{
.invalidURL:
.invalidResponse:
.unauthorized:
.forbidden:
.notFound:
.rateLimited:
.clientError( code, ):
.serverError( code):
.decodingError( error, ):
.unknown( code):
}
}
isRetryable: {
{
.rateLimited, .serverError:
:
}
}
}
Retry Logic with Exponential Backoff
extension APIClient {
func requestWithRetry<E: APIEndpoint>(
_ endpoint: E,
maxRetries: Int = 3,
initialDelay: Duration = .seconds(1)
) async throws -> E.Response {
var lastError: Error?
var delay = initialDelay
for attempt in 0..<maxRetries {
do {
return try await request(endpoint)
} catch let error as APIError where error.isRetryable {
lastError = error
if attempt < maxRetries - 1 {
try await Task.sleep(for: delay)
delay *= 2
}
} catch {
throw error
}
}
lastError .unknown(statusCode: )
}
}
Concrete Endpoint Example
enum ProductsAPI {
struct GetProducts: APIEndpoint {
typealias Response = [Product]
let path = "/products"
let method = HTTPMethod.get
var queryItems: [URLQueryItem]? {
[URLQueryItem(name: "page", value: "\(page)")]
}
let page: Int
}
struct CreateProduct: APIEndpoint {
typealias Response = Product
let path = "/products"
let method = HTTPMethod.post
var body: Encodable? { request }
let request: CreateProductRequest
}
struct DeleteProduct: APIEndpoint {
typealias Response = EmptyResponse
var path: String { "/products/\(id)" }
method .delete
id:
}
}
: {}
client (baseURL: (string: ))
client.setAuthToken()
products client.request(.(page: ))
newProduct client.request(.(request: .(name: )))
Troubleshooting
Common Issues
| Issue | Cause | Solution |
|---|
| "The certificate is not trusted" | SSL pinning or self-signed | Configure ATS or add certificate |
| "keyNotFound" | JSON key mismatch | Check CodingKeys, use keyDecodingStrategy |
| Request timeout | Network or server slow | Increase timeout, add retry |
| Memory spike on download | Not streaming | Use URLSession download task |
Debug Tips
extension URLRequest {
func log() {
print("➡️ \(httpMethod ?? "?") \(url?.absoluteString ?? "?")")
if let body = httpBody, let str = String(data: body, encoding: .utf8) {
print("Body: \(str)")
}
}
}
extension Data {
var prettyJSON: String {
guard let object = try? JSONSerialization.jsonObject(with: self),
let data = try? JSONSerialization.data(withJSONObject: object, options: .prettyPrinted),
let string = String(data: data, encoding: .utf8) else {
return String(data: self, encoding: .utf8) ?? "Invalid data"
}
return string
}
}
Validation Rules
validation:
- rule: use_async_await
severity: info
check: Prefer async/await over completion handlers
- rule: handle_all_errors
severity: error
check: All network calls must have error handling
- rule: no_hardcoded_urls
severity: warning
check: URLs should be configurable, not hardcoded
Usage
Skill("swift-networking")
Related Skills
swift-concurrency - Async patterns
swift-core-data - Caching responses
swift-testing - Mocking network calls