-
Notifications
You must be signed in to change notification settings - Fork 1
Best Practices and a Complete Example
Husnain Ali edited this page Sep 27, 2026
·
3 revisions
- Define one typed
Endpointper API operation; decode straight into your models. - Inject the
NetworkClient(or a wrapper protocol) so features are unit-testable. - Never hardcode tokens or base URLs; use
NetworkEnvironmentandTokenStorage. - Use
KeychainTokenStoragein production; keepInMemoryTokenStoragefor tests. - Pin the public key, not the certificate, and always deploy two pins so a rotation is not an outage.
- Do not blindly enable
retryNonIdempotent; make eachPOSTopt in only when it is genuinely safe to repeat. - Set
skipRequestQueue = trueon the token-refresh endpoint. - Keep sensitive fields out of logs by adding them to
redactedBodyKeysandredactedQueryItems. - Hop progress and completion callbacks to
@MainActorbefore touching UI state. - Treat
NetworkError.sessionExpiredas your single "log the user out" signal. - Call
await client.clearCache()when the signed-in user changes. See Caching.
A login, an authenticated call, an automatic refresh on expiry, and typed error handling.
import SwiftNetworkKit
// MARK: Models
struct Credentials: Encodable { let email: String; let password: String }
struct Session: Decodable { let accessToken: String; let refreshToken: String; let expiresIn: Int }
struct User: Decodable { let id: Int; let name: String; let email: String }
// MARK: Endpoints
struct LogIn: Endpoint {
typealias Response = Session
let credentials: Credentials
var method: HTTPMethod { .post }
var path: String { "/auth/login" }
var body: RequestBody? { .json(credentials) }
}
struct RefreshSession: Endpoint {
typealias Response = Session
let refreshToken: String
var method: HTTPMethod { .post }
var path: String { "/auth/refresh" }
var body: RequestBody? { .formURLEncoded(["refresh_token": refreshToken]) }
var skipRequestQueue: Bool { true } // must not deadlock behind a full queue
}
struct GetMe: Endpoint {
typealias Response = User
var path: String { "/me" }
var authentication: AuthRequirement { .required }
}
// MARK: Wiring
@MainActor
final class AccountService {
private let client: NetworkClient
private let storage = KeychainTokenStorage(service: "com.acme.app")
init(environment: NetworkEnvironment) {
var config = NetworkConfiguration(environment: environment)
config.tokenStorage = storage
config.retry = .default
config.metrics = InMemoryMetrics()
if environment.kind == .production {
config.sslPinning = .publicKeys(["sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="], hosts: ["api.acme.com"])
}
// capture nothing that would retain self; the refresh handler is @Sendable
let refreshClient = NetworkClient(configuration: config)
self.client = NetworkClient(
configuration: config,
refresh: { storage in
let current = try await storage.tokenPair()
let renewed = try await refreshClient.request(RefreshSession(refreshToken: current.refreshToken ?? ""))
return TokenPair(
accessToken: renewed.accessToken,
refreshToken: renewed.refreshToken,
expiryDate: Date().addingTimeInterval(TimeInterval(renewed.expiresIn))
)
},
onSessionExpired: { await AppRouter.shared.logout() }
)
}
func logIn(email: String, password: String) async throws {
let session = try await client.request(LogIn(credentials: .init(email: email, password: password)))
try await storage.setTokenPair(TokenPair(
accessToken: session.accessToken,
refreshToken: session.refreshToken,
expiryDate: Date().addingTimeInterval(TimeInterval(session.expiresIn))
))
}
/// If the access token has expired, this call triggers exactly one refresh, waits for it,
/// retries once, and returns the user. Concurrent callers share that single refresh.
func currentUser() async throws -> User {
do {
return try await client.request(GetMe())
} catch let error as NetworkError where error.code == .sessionExpired {
throw AppError.mustReauthenticate // refresh itself failed
}
}
}SwiftNetworkKit is source-available proprietary software (not open source). See FAQ Licensing and Support. Repo: https://github.com/ihusnainalii/SwiftNetworkKit · Docs: https://swiftnetworkkit.vercel.app/
Getting started
Guides
- Defining Endpoints
- Authentication and Token Refresh
- Security and Certificate Pinning
- Retry Policy
- Interceptors
- Request Management
- Caching
- Offline Request Queue
- Uploads Downloads and Progress
- Pagination and Batch
- Combine and SwiftUI
- Error Handling
- Observability
- Testing and Mocking
- Best Practices and a Complete Example
- Platform Notes, Thread Safety and Performance
Reference