Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

ANSCoder/Nexio

Nexio

Lightweight, actor-based Swift networking SDK

Swift Platforms SPM License CI codecov

Typed JSON requests · Auth strategies · Retry with backoff · Interceptor pipeline · SwiftUI image loading · Zero dependencies


Features

  • Actor-basedNexioClient is a Swift actor; zero data-race risk, safe to call from any concurrency context
  • Typed requestsget, post, put, patch, delete return decoded Decodable values directly
  • Type-safe endpointsEndpoint protocol for grouping request details in reusable structs
  • Auth strategies — bearer token, API key, custom headers, or dynamic provider (OAuth refresh)
  • Interceptor pipeline — adapt requests and retry failures with full control
  • Retry with backoff — none, linear, or exponential backoff; configurable per policy
  • SwiftUI image loadingNexioImage drop-in for AsyncImage with URLCache-backed caching and prefetch
  • Structured errorsNexioError covers network, auth, 4xx/5xx, and decoding failures
  • Zero dependencies — pure Swift, built on URLSession

Installation

Swift Package Manager

// Package.swift
dependencies: [
 .package(url: "https://github.com/ANSCoder/Nexio", from: "1.0.0")
],
targets: [
 .target(name: "YourApp", dependencies: [
 .product(name: "Nexio", package: "Nexio")
 ])
]

Or add it in Xcode: File → Add Package Dependencies, paste the repo URL.


Quick Start

Configure once at app launch

// AppDelegate / App.init
var config = NexioConfig()
config.baseURL = URL(string: "https://api.example.com")
config.timeout = 15
config.retry = .standard // 3 attempts, exponential backoff
config.logLevel = .errors
await NexioClient.shared.configure(config)
await NexioClient.shared.setAuth(.bearer("your-token"))

Make typed requests

// GET — decodes JSON automatically
let users: [User] = try await NexioClient.shared.get("/users")
// POST with body
let body = CreateUserRequest(name: "Alice")
let created: User = try await NexioClient.shared.post("/users", body: body)
// PUT / PATCH / DELETE
let updated: User = try await NexioClient.shared.put("/users/42", body: changes)
try await NexioClient.shared.delete("/users/42")
// Absolute URLs work too (ignores baseURL)
let user: User = try await NexioClient.shared.get("https://api.example.com/users/1")

Top-level shortcuts

// Shorthand for NexioClient.shared.get / .post
let users: [User] = try await nexioGet("/users")
let created: User = try await nexioPost("/users", body: newUser)

Authentication

// Static bearer token
await NexioClient.shared.setAuth(.bearer("jwt-token"))
// API key in a custom header
await NexioClient.shared.setAuth(.apiKey(header: "X-Api-Key", value: "secret"))
// Arbitrary headers
await NexioClient.shared.setAuth(.custom(["X-Tenant-ID": "acme", "X-Version": "2"]))
// Dynamic token — closure called before every request (ideal for OAuth refresh)
let authInterceptor = AuthInterceptor {
 await TokenStore.shared.currentToken() // returns AuthStrategy
}
await NexioClient.shared.addInterceptor(authInterceptor)

Per-endpoint auth override

struct PublicEndpoint: Endpoint {
 var baseURL: URL { URL(string: "https://api.example.com")! }
 var path: String { "/status" }
 var method: HTTPMethod { .get }
 var auth: AuthStrategy? { .some(.none) } // skip global auth for this request
}

Type-Safe Endpoints

Group URL, method, query params, headers, and body in one reusable struct:

struct GetUser: Endpoint {
 let id: Int
 var baseURL: URL { URL(string: "https://api.example.com")! }
 var path: String { "/users/\(id)" }
 var method: HTTPMethod { .get }
}
struct SearchUsers: Endpoint {
 let query: String
 var baseURL: URL { URL(string: "https://api.example.com")! }
 var path: String { "/users/search" }
 var method: HTTPMethod { .get }
 var queryItems: [URLQueryItem] { [URLQueryItem(name: "q", value: query)] }
}
let user: User = try await NexioClient.shared.request(GetUser(id: 42))
let results: [User] = try await NexioClient.shared.request(SearchUsers(query: "alice"))

Retry

// Via config (recommended — applied automatically)
config.retry = .standard // 3 retries, exponential backoff
config.retry = RetryPolicy(maxAttempts: 5,
 backoff: .linear(seconds: 2)) // custom
// Via interceptor (for per-client or programmatic control)
await NexioClient.shared.addInterceptor(
 RetryInterceptor(policy: .standard)
)

Retried automatically on: .noInternet, .timeout, and 5xx serverError.
Not retried: 4xx errors (client errors are not transient).


Interceptors

Implement Interceptor to hook into every request:

struct LoggingInterceptor: Interceptor {
 func adapt(_ request: URLRequest, for session: URLSession) async throws -> URLRequest {
 print("\(request.httpMethod ?? "")\(request.url?.absoluteString ?? "")")
 return request
 }
 func retry(_ request: URLRequest, dueTo error: NexioError, attempt: Int) async -> Bool {
 false
 }
}
await NexioClient.shared.addInterceptor(LoggingInterceptor())

Interceptors run in insertion order during adapt, and reverse order during retry.


Error Handling

All errors are typed as NexioError:

do {
 let user: User = try await NexioClient.shared.get("/users/1")
} catch NexioError.unauthorized {
 // Redirect to login
} catch NexioError.notFound {
 // Show 404 UI
} catch NexioError.noInternet {
 // Show offline banner
} catch NexioError.serverError(let statusCode, let data) {
 // Handle 5xx
} catch NexioError.decodingFailed(let underlying, let data) {
 // Log raw response for debugging
 print(String(data: data, encoding: .utf8) ?? "")
} catch NexioError.invalidURL(let string) {
 // Bad URL at call site
}

Image Loading (SwiftUI)

NexioImage is a drop-in replacement for AsyncImage with transparent URLCache caching (50 MB memory / 200 MB disk by default):

// Default — gray placeholder, photo icon on failure
NexioImage("https://cdn.example.com/photo.jpg")
 .frame(width: 100, height: 100)
 .clipShape(Circle())
// Custom placeholder and failure views
NexioImage(
 "https://cdn.example.com/photo.jpg",
 placeholder: { ProgressView() },
 failureImage: { Image(systemName: "person.crop.circle.fill") }
)
// Prefetch a list for smoother scroll performance
let urls = items.compactMap { URL(string: 0ドル.imageURL) }
await ImageLoader.shared.prefetch(urls)
// Clear cache
await ImageLoader.shared.clearCache()

Concurrency

NexioClient is a Swift actor — all state mutations are serialized automatically with no extra effort. In-flight network I/O runs concurrently through URLSession's connection pool:

// Three requests in flight simultaneously
async let users: [User] = NexioClient.shared.get("/users")
async let posts: [Post] = NexioClient.shared.get("/posts")
async let comments: [Comment] = NexioClient.shared.get("/comments")
let (u, p, c) = try await (users, posts, comments)

Actor isolation serializes only the microsecond-scale bookkeeping (building requests, applying headers, decoding JSON). Network round-trips never block other callers.


Testing

Inject a custom URLProtocol subclass via NexioConfig.protocolClasses to stub responses without hitting the network:

var config = NexioConfig()
config.protocolClasses = [MockURLProtocol.self]
await client.configure(config)

Requirements

Platform Minimum
iOS 16.0
macOS 13.0
watchOS 9.0
tvOS 16.0

Swift 6.2+ · Zero external dependencies


License

Nexio is released under the MIT license. See LICENSE for details.

About

Lightweight, actor-based Swift networking SDK — typed requests, auth strategies, retry/interceptor pipeline, and SwiftUI image loading, with zero external dependencies.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

Contributors

Languages

AltStyle によって変換されたページ (->オリジナル) /