The protocol-oriented networking engine for Swift.
A production-grade, composable Apple-platform networking layer with actor-isolated single-flight 401 refresh, zero-dependency SPKI SSL pinning, OAuth 2.0 PKCE, a persisted offline replay queue, and RFC 7578 disk uploads.
https://github.com/ihusnainalii/SwiftNetworkKitVisual request lifecycle & pipeline
Seven real client.request() runs, captured stage by stage through SwiftNetworkKit's own interceptor and transport hooks. Pick a scenario, then press play or step through it. Durations are wall-clock against MockNetworkTransport, so the timings are the pipeline, not the internet.
01. Auth + request build
stageThe endpoint and the environment are folded into one URLRequest: path parameters, query items and headers merged, the body encoded, then the auth strategy attaches credentials (or the TokenManager actor injects a live access token).
RequestBuilder · TokenManager
trace: client.request(Get())
What to watch
The baseline. Almost nothing is spent outside the transport, the pipeline overhead itself is microseconds.
SwiftNetworkKit v2.0.0 · macOS 15.7.9 · arm64 · 2026-09-27 · source (v2.0.0)
Real URL request playground & code generator
Type any live REST API URL, execute the real HTTP request directly, and get instant, type-safe Swift 6 Endpoint and Codable models tailored for the SwiftNetworkKit SPM package.
Live HTTP Request BuilderInteractive
// ==========================================
// SwiftNetworkKit Swift 6 Generated Implementation
// Package: https://github.com/ihusnainalii/SwiftNetworkKit
// ==========================================
import SwiftNetworkKit
import Foundation
// 1. Type-Safe Endpoint Definition
public struct GitHubUserEndpoint: Endpoint {
public typealias Response = GitHubUser
public var path: String {
"/users/octocat"
}
public var method: HTTPMethod {
.get
}
public var authentication: AuthRequirement {
.none
}
public var headers: HTTPHeaders {
[
"Accept": "application/vnd.github.v3+json"
]
}
}
// --- Decodable Response Model ---
public struct GitHubUser: Codable, Sendable {
public let id: Int
public let name: String
public let status: String
public let createdAt: Date?
}
// --- Client Configuration ---
import SwiftNetworkKit
// 2. Initialize NetworkClient
var config = NetworkConfiguration(
baseURL: URL(string: "https://api.github.com")!,
defaultHeaders: ["User-Agent": "MyiOSApp/1.0"]
)
// Public endpoint without auth
config.retry = .standard // Exponential backoff + full jitter + Retry-After
// Caching disabled
// Offline queueing disabled
// SPKI SHA-256 Public Key Pinning (Renewal-safe)
config.sslPinning = SSLPinningConfiguration(
pinnedHashes: ["api.github.com": ["sha256/9kE7yZ6W+V2r0x7G3h5...="]]
)
config.logger = RedactingLogger(rules: RedactionRule.standardRules, logLevel: .verbose)
let client = NetworkClient(configuration: config)
// --- Call Execution ---
// 3. Modern Swift 6 Async/Await Execution
do {
let endpoint = GitHubUserEndpoint()
let result: GitHubUser = try await client.request(endpoint)
print("Received typed response: \(result)")
} catch let error as NetworkError {
switch error {
case .unauthorized(let reason):
print("Unauthorized: \(reason ?? "Token expired")")
case .sslPinningFailed(let host):
print("Security warning: SSL pin mismatch on \(host)")
case .httpError(let statusCode, _, let context):
print("HTTP Error \(statusCode) on \(context.requestURL)")
case .offline:
print("Device is offline")
default:
print("Network request failed: \(error.localizedDescription)")
}
}Concurrency & actor isolation
Several real requests fired at once through one NetworkClient. Press play to sweep the timeline, or click any dot to see exactly what that actor did and when.
6 identical requests in flight, 1 reached the transport, 5 coalesced onto it
SwiftNetworkKit v2.0.0 · macOS 15.7.9 · arm64 · 2026-09-27 · source (v2.0.0)
Visual telemetry, caching & concurrency models
Interactive latency waterfalls, two-tier cache hit analytics, exponential jitter curves, single-flight actor throughput, and network resource savings.
Two-Tier Cache Latency Breakdown (ms)
Actor-isolated pointer retrieval. No disk reads, zero CPU thread contention.
Local SSD read, schema validated, persists across application restarts and device reboots.
Cellular radio wake-up latency + TLS 1.3 handshake + backend processing + TCP packet transfer.
Cache Hit Distribution94% Total Hit Ratio
// Declarative Two-Tier Cache with Stale-While-Revalidate
struct FeedEndpoint: Endpoint {
typealias Response = FeedResponse
var path: String { "/feed" }
var cachePolicy: CachePolicy {
.staleWhileRevalidate(maxAge: 60, staleWindow: 300)
}
}Stale-While-Revalidate (SWR) & TTL Lifecycle
Instant UI response from cache while background actor silently fetches freshest data.
STATUS: STALE-WHILE-REVALIDATE ACTIVE (30s to 90s)
Instantly yields cached data to UI (0.4ms) + launches asynchronous Swift actor task in background to update cache without UI latency!
How SwiftNetworkKit compares
See why modern Swift 6 projects choose SwiftNetworkKit over legacy callback frameworks, reactive overhead, code-gen clutter, or bare URLSession boilerplate.
SwiftNetworkKit vs. Alamofire
Modern Swift 6 Strict Concurrency vs. Legacy Callback Architecture
SwiftNetworkKit Advantages
- Pure Swift 6 Actor-isolated concurrency with 0 compiler data races
- Zero external dependencies (pure Apple standard frameworks)
- Single-flight 401 token refresh built natively into the actor layer
- Built-in SPKI SHA-256 Public Key Pinning with development discovery mode
- Automatic Bearer token & password redaction in logs
- Persisted offline request queue with automatic FIFO replay on reconnect
Alamofire Tradeoffs
- Historical codebase originally designed around completion handlers & GCD locks
- Token refresh retriers require manual mutex / semaphore synchronizations
- Heavier binary footprint with thousands of lines of legacy code
- Logging does not redact sensitive Bearer tokens out-of-the-box
- No built-in persisted offline request queue or SwiftUI @Observable bindings
// SwiftNetworkKit (Pure Swift 6 & Actor Isolated)
let client = NetworkClient(
configuration: NetworkConfiguration(
baseURL: "https://api.acme.com",
tokenStorage: KeychainTokenStorage(service: "com.acme.app")
),
refresh: { storage in
try await authService.refreshToken(storage.refreshToken())
}
)
struct GetFeed: Endpoint {
typealias Response = [FeedItem]
var path: String { "/feed" }
var authentication: AuthRequirement { .required }
}
let feed = try await client.request(GetFeed())// Alamofire (Requires RequestInterceptor / Lock State Machine)
class OAuthAuthenticator: RequestInterceptor {
private let lock = NSLock()
private var isRefreshing = false
private var requestsToRetry: [(RetryResult) -> Void] = []
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
lock.lock(); defer { lock.unlock() }
// Complex manual queue management & lock state handling...
}
}
let session = Session(interceptor: OAuthAuthenticator())
let feed = try await session.request("https://api.acme.com/feed")
.serializingDecodable([FeedItem].self)
.value| Capability / Architecture | SwiftNetworkKit | Alamofire | Moya | Vanilla URLSession |
|---|---|---|---|---|
Swift 6 Strict Concurrency 13 isolated actors, 0 data races, Sendable guarantees | Native Swift 6 | Partial (v5.9+) | Legacy Rx roots | Manual sync |
External Dependencies Zero third-party code in compiled binary | 0 Dependencies | 0 Dependencies | Multiple packages | 0 (Built-in) |
Single-Flight 401 Token Refresh Actor-isolated queueing without race conditions | Built-in Actor | Manual Retrier | Not built-in | DIY boilerplate |
SPKI Public Key SHA-256 Pinning Survives certificate renewal, zero extra libs | Native SPKI | ServerTrust | Via Alamofire | Complex C-APIs |
RFC 7578 Multipart Disk Streaming 500MB+ file uploads directly from disk without OOM | Zero-Copy Disk Stream | MultipartFormData | Basic Wrappers | Manual Byte Buffers |
Persisted Offline Request Queue Encrypted FIFO spool with automatic reconnect drain | Built-in Spool Actor | None | None | None |
Two-Tier Cache & Stale-While-Revalidate Memory + DiskCacheStore with SHA-256 & ETag 304 | L1 + L2 with SWR | Basic URLCache | None | URLCache only |
Jittered Retry & Rate-Limiting Full & equal jitter, Retry-After header parsing | Full Jitter + Headers | Basic Backoff | External / Plugin | None |
SwiftUI @Observable & AsyncSequence NetworkResource state container for iOS 17+ Observation | Native NetworkResource | None | Combine only | None |
Zero-Overhead In-Memory Metrics Microsecond P50/P90/P95 latency tracking & error histograms | Native NetworkMetrics | EventMonitor | None | None |
Privacy Redacting Logger Automatic secret, token & auth header masking | Automatic Redaction | Raw Logs | Raw Logs | None |
Mock Transport & Deterministic Tests Protocol-based mock transport with 0 network calls | MockTransport Protocol | URLProtocol Stubbing | SampleData (Enum) | URLProtocol subclass |
Modular architecture & 15 engine subsystems
Explore the decoupled, actor-isolated modules powering SwiftNetworkKit, from pure Swift 6 Sendable value types to native SwiftUI Observation.
Scaffold & Protocol-Oriented DSL
Package scaffold, immutable value-type models, and protocol-oriented Endpoint DSL under Swift 6 strict concurrency.
Primary Exported Types
Architectural Capabilities & Guarantees
- Protocol-oriented Endpoint DSL with associated type Response
- Sendable RequestBody (Data, JSON, URLEncoded, Multipart)
- Strongly typed NetworkError hierarchy with underlying Sendable error boxing
- Zero external runtime dependencies
Production Code ExampleSwift 6 strict concurrency
struct GetProfileEndpoint: Endpoint {
typealias Response = UserProfile
var path: String { "/v1/profile" }
var method: HTTPMethod { .get }
var authRequirement: AuthRequirement { .bearer }
}Enterprise features built for scale
Engineered with clean architectural separation, zero third-party dependencies, and native Swift 6 concurrency primitives.
Swift 6 Strict Concurrency
Fully annotated with Sendable conformances and thread-safe actor boundaries. Guarantees 0 data races under the Swift 6 compiler.
Single-Flight 401 & OAuth PKCE
Actor-isolated token refresh ensures 10 simultaneous 401s trigger exactly one refresh. Full RFC 7636 PKCE Authorization Code flow built-in.
SPKI Public Key Pinning
Zero third-party security libs. Pins raw Subject Public Key Info SHA-256 hashes or bundled certificates with native Trust evaluation.
Persisted Offline Replay Queue
Opt-in per endpoint. Queues requests when offline into encrypted FileOfflineStore and automatically replays FIFO upon network restoration.
Memory & Disk HTTP Caching
DiskCacheStore & MemoryCacheStore with ETag / 304 revalidation, stale-while-revalidate, and network-failure fallback.
Jitter & Rate Limit Engine
Full and equal jitter prevent thundering herds on microservices. Automatically parses and respects HTTP 429 / 503 Retry-After headers.
RFC 7578 Multipart Disk Uploads
Streams gigabyte-sized files straight from local disk into the transport socket with real-time byte progress reporting (ProgressEvent).
Pagination as AsyncSequence
PaginatedEndpoint yields AsyncThrowingStream pages automatically. Includes client.zip and client.batch for parallel execution.
SwiftUI @Observable & Combine
NetworkResource<Value> and Paged<Item> state containers for iOS 17+ Observation, plus optional Combine publishers (#if canImport).
Redacting Privacy Logger
Logs cURL commands and status codes without ever leaking Bearer tokens, cookies, or sensitive API keys in the Xcode debug console.
NWPathMonitor Reachability
PathNetworkMonitor wraps Network framework, yielding statusUpdates() and connectionRestored() AsyncStreams.
Shipped Test Doubles
MockNetworkTransport (FIFO queue & pattern stubs), URLProtocolStub, TestClock (fast-forward time), and CapturingLogger ship directly in the library.
1.0.0 & beyond
Everything the 1.0.0 milestone delivered, plus the capabilities planned for SwiftNetworkKit after it.
1.0.0 Stabilization
Current Phase: v2.0.0 released, public API frozen
Public vs package access levels audited; leaked internals made internal; test hooks moved behind @_spi(SwiftNetworkKitTesting).
Thirteen per-topic DocC guides, built by CI and hosted at /docs on this site.
Library line coverage at 90%, enforced by a floor in scripts/coverage.sh; ThreadSanitizer & AddressSanitizer on every push.
Full test suite on Linux (Foundation subset) and macOS/iOS; tvOS, watchOS and visionOS build in CI.
NetworkKitBenchmarks covers pipeline overhead, interceptor-chain scaling, cache and redaction costs; recorded traces power this site.
Post-1.0 Planned Features
Architecture Extensions
- WebSocket & Server-Sent Events (SSE) Endpoint type (AsyncSequence of frames)
- GraphQL Helper Layer (Query + Variables to Endpoint with typed errors)
- Background URLSession transfer support with app-side completion handler
- Response-Body Streaming (client.stream(endpoint) -> AsyncSequence<Data>)
- swift-log Bridge (Shipped as standalone product to keep core dependency-free)
- Request/Response VCR Fixture Recorder built on URLProtocolStub
Benchmarks
The NetworkKitBenchmarks target runs the real package against its own MockNetworkTransport, so these are SwiftNetworkKit's own overhead, with the internet taken out of the picture. Regenerated on every release.
Median client.request() overhead as the request and response interceptor chain grows. The pipeline stays flat: 16 interceptors cost roughly the same as none.
Median time for the hot-path building blocks, in microseconds. A memory-cache round-trip is under 1µs; a cache-served request skips the network entirely.
| Operation | Median |
|---|---|
| Request pipeline overheadclient.request() end to end, network replaced by MockNetworkTransport | 41.5 µs/op |
| Pipeline + 1 interceptorsrequest + response interceptor chain of 1 | 37.0 µs/op |
| Pipeline + 4 interceptorsrequest + response interceptor chain of 4 | 39.4 µs/op |
| Pipeline + 16 interceptorsrequest + response interceptor chain of 16 | 35.6 µs/op |
| JSON decode (1 object) | 4.83 µs/op |
| JSON decode (64 objects) | 272.7 µs/op |
| Memory cache write + read | 0.88 µs/op |
| Disk cache write + read | 721.4 µs/op |
| Cache-served requestclient.request() answered from the response cache (no transport hit) | 34.7 µs/op |
| Header redaction | 1,167 ns/op |
| JSON body redaction | 6.50 µs/op |
| Deduplicate 150 concurrent requests150 identical requests in flight at once, one 5 ms backend call | 27.0 ms/op |
| Requests coalesced per backend call6000 requests issued, 43 reached the transport | 140× x |
| Single-flight 401 refresh (50 concurrent)50 parallel 401s, one token refresh, all retried | 1.23 ms/op |
| Metrics event recording | 541.0 ns/op |
SwiftNetworkKit v2.0.0 · macOS 15.7.9 · arm64 · 3 cores · release build · 2026-09-27 · source (v2.0.0)
Comprehensive public API reference
A curated tour of the most-used types with copy-pasteable examples. The full generated list of every public type is in Documentation/APIReference.md (from the DocC symbol graph, 101 types).
NetworkClient
The main networking orchestrator. Dispatches requests through interceptors, URLSession transport, actor token refresh loops, two-tier cache stores, and metrics collectors.
public final class NetworkClient: Sendablelet config = NetworkConfiguration(baseURL: "https://api.acme.com")
let client = NetworkClient(configuration: config)
let profile: UserProfile = try await client.request(GetProfileEndpoint())Endpoint
Protocol defining a type-safe HTTP request with an associated Decodable response type, path, method, headers, query items, body, and policies.
public protocol Endpoint: Sendablestruct ListUsersEndpoint: Endpoint {
typealias Response = [User]
var path: String { "/v1/users" }
var method: HTTPMethod { .get }
var queryItems: [URLQueryItem]? { [URLQueryItem(name: "limit", value: "25")] }
var authRequirement: AuthRequirement { .bearer }
}NetworkConfiguration
Central configuration holding base URL, default headers, timeout intervals, retry policies, SSL pinning, cache configuration, and logging.
public struct NetworkConfiguration: Sendablevar config = NetworkConfiguration(baseURL: "https://api.acme.com")
config.timeoutInterval = 30.0
config.retry = .standard
config.cache = CacheConfiguration(store: DiskCacheStore(), defaultPolicy: .cacheFirst)
config.sslPinning = .publicKeys(["sha256/k2v657xMp4bCWqJaQDZrU3J38RxQL0WPSnguE/9czoq="])RequestBody
Encapsulates payload serialization for JSON, binary raw data, URL-encoded form data, and multipart streaming uploads.
public enum RequestBody: Sendable// JSON payload with custom encoder
var body = RequestBody.json(DraftPost(title: "Hello World"))
// Multipart payload
var body = RequestBody.multipart(form)NetworkError
Strongly typed error enum covering encoding/decoding failures, transport errors, HTTP non-2xx status codes, rate limiting, and SSL pinning rejections.
public enum NetworkError: Error, Sendable, Equatabledo {
let result = try await client.request(endpoint)
} catch let NetworkError.httpError(statusCode, data, context) {
print("HTTP \(statusCode) failed on \(context.requestURL)")
} catch NetworkError.sslPinningFailed(let host) {
print("Untrusted certificate on \(host)")
}TokenManager
Thread-safe actor managing single-flight 401 refresh token exchanges, caller queueing, refresh loops guards, and proactive expiration renewal.
public actor TokenManagerlet manager = TokenManager(
storage: KeychainTokenStorage(service: "com.acme.app"),
refreshHandler: { expiredToken in
try await authClient.refreshToken(expiredToken)
}
)
let token = try await manager.validAccessToken()KeychainTokenStorage
Production iOS/macOS Keychain storage for TokenPair with device-only kSecAttrAccessibleAfterFirstUnlock protection.
public final class KeychainTokenStorage: TokenStorage, @unchecked Sendablelet storage = KeychainTokenStorage(
service: "com.acme.app",
accessibility: .afterFirstUnlockThisDeviceOnly
)BearerAuth
AuthStrategy injecting Authorization: Bearer <token> into outgoing URLRequests via an asynchronous token provider.
public struct BearerAuth: AuthStrategylet auth = BearerAuth {
try await tokenManager.validAccessToken()
}AuthorizationCodeFlow
OAuth 2.0 RFC 7636 Authorization Code flow with PKCE. Generates authorization URLs, computes SHA-256 code verifiers, and exchanges tokens.
public struct AuthorizationCodeFlow: Sendablelet flow = AuthorizationCodeFlow(
clientId: "mobile-client",
authorizeURL: URL(string: "https://auth.acme.com/authorize")!,
tokenURL: URL(string: "https://auth.acme.com/token")!,
redirectURI: URL(string: "acme://callback")!,
scopes: ["openid", "profile", "offline_access"]
)
let (authURL, verifier, state) = flow.makeAuthorizationURL()SSLPinningConfiguration
Subject Public Key Info (SPKI) SHA-256 hash pinning preventing certificate expiration lockouts while securing against MitM interception.
public struct SSLPinningConfiguration: Sendablelet pinning = SSLPinningConfiguration(
pinnedHashes: [
"api.acme.com": ["sha256/k2v657xMp4bCWqJaQDZrU3J38RxQL0WPSnguE/9czoq="]
],
allowBackupKeys: true
)ServerTrustEvaluator
Runs SecTrustEvaluateWithError, then matches the server's SPKI SHA-256 pins. Returns a ServerTrustDecision (.pinned / .notPinned / .rejected) that the transport delegate turns into a URLSession challenge disposition.
public struct ServerTrustEvaluator: ServerTrustEvaluating// Configured for you when NetworkConfiguration.sslPinning is set:
let config = NetworkConfiguration(
environment: env,
sslPinning: .publicKeys(["api.acme.com": ["sha256/AAAA...", "sha256/BBBB..."]])
)DiskCacheStore
Disk-backed LRU HTTP response cache. Encrypted at rest with NSFileProtectionCompleteUnlessOpen. Validates Cache-Control max-age and ETag 304.
public actor DiskCacheStore: ResponseCachelet diskCache = DiskCacheStore(directory: cacheDirURL, limitBytes: 100_000_000)
await diskCache.setValue(cachedResponse, forKey: "sha256_hash")CachePolicy
Per-request cache strategy. Set it on NetworkConfiguration.cache.defaultPolicy or override per Endpoint.
public enum CachePolicy: Sendable, Hashablepublic enum CachePolicy {
case ignoreCache
case networkOnly
case cacheFirst
case networkFirst
case cacheOnly
case staleWhileRevalidate
}OfflineRequestQueue
Persisted offline request queue. Spools non-idempotent write endpoints to disk when disconnected and automatically drains FIFO upon network reconnection.
public actor OfflineRequestQueuelet offlineQueue = OfflineRequestQueue(store: FileOfflineStore(), client: client, monitor: monitor)
let queueID = try await offlineQueue.enqueue(CreateOrderEndpoint(order: newOrder))FileOfflineStore
File-system backed offline store saving serialized endpoints in an encrypted JSON format across application terminations.
public actor FileOfflineStore: OfflineStorelet store = FileOfflineStore(directory: offlineSpoolURL)RetryPolicy
Configures maximum retry attempts, exponential backoff curves, full/equal jitter, and HTTP 429 Retry-After compliance.
public struct RetryPolicy: Sendablelet policy = RetryPolicy(
maxRetries: 3,
backoff: ExponentialBackoff(initialDelay: 0.5, maxDelay: 8.0, jitter: .full),
retryableStatusCodes: [408, 429, 500, 502, 503, 504]
)Redactor
Redacts sensitive headers (authorization, cookie, set-cookie, proxy-authorization always) and configured JSON body keys before a line reaches the NetworkLogger. NetworkClient applies it automatically.
public struct Redactor: Sendablelet config = NetworkConfiguration(
environment: env,
redactedHeaders: ["x-api-key"],
redactedBodyKeys: ["password", "otp"]
)PathNetworkMonitor
Real-time network reachability monitor wrapping Apple Network.framework NWPathMonitor, broadcasting status changes over an AsyncStream.
public final class PathNetworkMonitor: NetworkMonitor, Sendablelet monitor = PathNetworkMonitor()
for await status in monitor.statusStream {
if status.isConnected {
print("Connected via \(status.connectionType)")
}
}MultipartFormData
RFC 7578 compliant multipart/form-data generator. Streams large binary files directly from disk without exhausting memory.
public struct MultipartFormData: Sendablevar form = MultipartFormData()
form.append(value: "Husnain", name: "author")
form.append(fileData: imageBytes, name: "avatar", fileName: "profile.png", mimeType: "image/png")
let (bodyData, boundary) = form.build()ProgressEvent
Asynchronous progress notification containing fractionCompleted, bytesTransferred, totalBytes, and optional final decoded response.
public struct ProgressEvent<T: Sendable>: Sendablefor try await event in client.upload(UploadAvatarEndpoint(), from: avatarFileURL) {
print("Uploaded \(event.bytesTransferred) of \(event.totalBytes) bytes (\(Int(event.fractionCompleted * 100))%)")
}RequestInterceptor
Adapts an outgoing URLRequest just before it is sent (after auth and tracing). Use for signing, feature flags, locale, extra headers. Pair with ResponseInterceptor for the inbound side.
public protocol RequestInterceptor: Sendablestruct SignRequest: RequestInterceptor {
func adapt(_ request: URLRequest, for endpoint: AnyEndpoint) async throws -> URLRequest {
var r = request
r.setValue(sign(r), forHTTPHeaderField: "X-Signature")
return r
}
}RequestPriority
Per-endpoint queue priority (.low / .normal / .high). The bounded request queue (NetworkConfiguration.maxConcurrentRequests) admits higher-priority waiters first.
public enum RequestPriority: Sendable, Comparablestruct Checkout: Endpoint {
typealias Response = Receipt
var path: String { "/checkout" }
var priority: RequestPriority { .high }
}PaginatedEndpoint
Protocol declaring paginated APIs. Provides nextEndpoint(after:) mapping to yield an infinite AsyncThrowingStream of items or pages.
public protocol PaginatedEndpoint: Endpointfor try await page in client.paginate(ListTransactionsEndpoint(cursor: nil)) {
print("Page received with \(page.count) items")
}MockNetworkTransport
Comprehensive test double shipping inside the package. Register stubs by Endpoint type, HTTP status, or URLRequest predicates without network flakiness.
public final class MockNetworkTransport: NetworkTransport, @unchecked Sendablelet mock = MockNetworkTransport()
mock.registerStub(for: GetProfileEndpoint.self, result: .success(UserProfile.mock))
let client = NetworkClient(configuration: .mock, transport: mock)NetworkResource
SwiftUI observable state container managing async loading, reload/refresh, and binding states (.idle, .loading, .success, .failure).
@Observable @MainActor public final class NetworkResource<Value: Sendable>@Observable @MainActor
final class ProfileViewModel {
let user = NetworkResource<UserProfile>(client: client)
func onAppear() async {
await user.load(GetProfileEndpoint())
}
}The Modern Swift 6 Open Source Suite
Two complementary, protocol-oriented engines engineered with zero third-party dependencies, actor isolation, and complete Swift 6 strict concurrency safety.
SwiftNetworkKit
v2.0.0Networking Engine
Protocol-oriented networking engine with actor-isolated single-flight 401 token refresh, zero-dependency SPKI public key SSL pinning, RFC 7578 disk uploads, and offline replay queues.
- Swift 6 strict concurrency with zero data races
- Single-flight 401 token refresh coalescing
- Native SPKI SHA-256 public key pinning
- Zero external third-party dependencies
// 1. Strongly typed endpoint
let profile: User = try await client.request(GetProfile())SwiftLocalStorage
v0.6.0Persistence & Caching Engine
Zero-boilerplate persistence and caching framework for Apple platforms. Persists pure Codable DTOs on SwiftData envelopes with ModelActor isolation, TTL cache purge, indexed queries, and reactive SwiftUI feeds.
- Zero @Model boilerplate — persist pure Codable structs
- @ModelActor isolation with 0 data races
- Smart TTL cache expiration & automatic background purge
- Live reactive SwiftUI observation feeds (AsyncSequence)
// 2. Persist Codable DTO with 1-hour TTL cache
try await storage.save(profile, expiration: .hours(1))Better Together: Seamless Network + Storage Pipeline
Fetch remote data with SwiftNetworkKit and immediately cache or persist it with SwiftLocalStorage with automatic TTL expiration, zero data races, and live UI updates.
Add to your project in seconds
Install SwiftNetworkKit via Xcode Package Manager, Package.swift manifest, or Swift CLI.
- In Xcode, select File ➔ Add Package Dependencies...
- Paste the repository URL into the search bar:
https://github.com/ihusnainalii/SwiftNetworkKitSelect Dependency Rule: Up to Next Minor Version starting from 2.0.0.
Questions, feedback, or collaboration
I build and maintain SwiftNetworkKit. Open a GitHub issue for bugs and feature requests, or reach me directly through any of the channels below.
Mail me directly for consulting, partnerships, or anything that doesn't belong in a public issue. I usually reply within a day or two.
GitHub
@ihusnainaliiBrowse the source, releases, and changelog here, or star the repo. My other projects live on this profile too.
If you want to see my full work history and background, or just connect, find me here.
Portfolio
ihusnainalii.github.ioSee the apps I've shipped, case studies, and my writing on iOS and Swift.
Bug reports & features
GitHub IssuesHit a bug or want a feature? Open an issue with a repro and I'll take a look. This is the fastest way to reach me.
Based in
Riyadh, Saudi Arabia
I'm on GMT+3 (Arabia Standard Time). I take calls roughly 9am to 7pm local, and I'm happy to work async if you're in another timezone.