A lightweight, Swift-6-ready networking library designed for modern iOS apps using async/await, clean architecture, and testable abstractions.
π¬ Join the discussion. Feedback and questions welcome
- β
Native
async/awaitAPI - β Protocol-based, fully mockable networking layer
- β Typed request / response decoding
- β Swift 6 + Swift Concurrency friendly
- β Designed for MVVM / Clean Architecture
- β Zero third-party dependencies
- β Built-in canned response transports for testing
A runnable SwiftUI demo app is included in this repository using a local package reference.
- Clone the repository:
git clone https://github.com/gentle-giraffe-apps/GentleNetworking.git
- Open the demo project:
Demo/GentleNetworkingDemo/GentleNetworkingDemo.xcodeproj - Select an iOS 17+ simulator.
- Build & Run (βR).
The project is preconfigured with a local Swift Package reference to GentleNetworking and should run without any additional setup.
- Open your project in Xcode
- Go to File β Add Packages...
- Enter the repository URL:
https://github.com/gentle-giraffe-apps/GentleNetworking.git - Choose a version rule (or
mainwhile developing) - Add the GentleNetworking product to your app target
This project enforces quality gates via CI and static analysis:
- CI: All commits to
mainmust pass GitHub Actions checks - Static analysis: DeepSource runs on every commit to
main.
The badge indicates the current number of outstanding static analysis issues. - Test coverage: Codecov reports line coverage for the
mainbranch
These checks are intended to keep the design system safe to evolve over time.
GentleNetworking is centered around a single, protocol-driven HTTPNetworkService that coordinates requests using injected endpoint, environment, and authentication abstractions.
flowchart TB
HTTP["HTTPNetworkService<br/><br/>- request(...)"]
Endpoint["EndpointProtocol<br/><br/><br/>"]
Env["APIEnvironmentProtocol<br/><br/><br/>"]
Auth["AuthServiceProtocol<br/><br/><br/>"]
HTTP --> Endpoint
HTTP --> Env
HTTP -->|injected| Auth
flowchart TB
APIEndpoint["APIEndpoint enum<br/><br/>case endpoint1<br/>β¦<br/>endpointN"]
EndpointProtocol["EndpointProtocol<br/><br/>- path<br/>- method<br/>- query<br/>- body<br/>- requiresAuth"]
APIEndpoint -->|conforms to| EndpointProtocol
import GentleNetworking
let apiEnvironment = DefaultAPIEnvironment(
baseURL: URL(string: "https://api.company.com")
)
nonisolated enum APIEndpoint: EndpointProtocol {
case signIn(username: String, password: String)
case model(id: Int)
case models
var path: String {
switch self {
case .signIn: "/api/signIn"
case .model(let id): "/api/model/\(id)"
case .models: "/api/models"
}
}
var method: HTTPMethod {
switch self {
case .signIn: .post
case .model, .models: .get
}
}
var query: [URLQueryItem]? {
switch self {
case .signIn, .model, .models: nil
}
}
var body: [String: EndpointAnyEncodable]? {
switch self {
case .signIn(let username, let password): [
"username": EndpointAnyEncodable(username),
"password": EndpointAnyEncodable(password)
]
case .model, .models: nil
}
}
var requiresAuth: Bool {
switch self {
case .model, .models: true
case .signIn(username: _, password: _): false
}
}
} let networkService = HTTPNetworkService() let keyChainAuthService = SystemKeyChainAuthService()
struct AuthTokenModel: Decodable, Sendable {
let token: String
}
let authTokenModel: AuthTokenModel = try await networkService.request(
to: .signIn(username: "user", password: "pass"),
via: apiEnvironment
)
try await keyChainAuthService.saveAccessToken(
authTokenModel.token
) struct Model: Decodable, Sendable {
let id: Int
let property: String
}
let model: Model = try await networkService.request(
to: .model(id: 123),
via: apiEnvironment
) let models: [Model] = try await networkService.requestModels(
to: .models,
via: apiEnvironment
)GentleNetworking provides a transport-layer abstraction for easy mocking in tests.
Returns a fixed response for any request:
let transport = CannedResponseTransport(
string: #"{"id": 1, "title": "Test"}"#,
statusCode: 200
)
let networkService = HTTPNetworkService(transport: transport)Match requests by method and path pattern for more realistic test scenarios:
let transport = CannedRoutesTransport(routes: [
CannedRoute(
pattern: RequestPattern(method: .get, path: "/api/models"),
response: CannedResponse(string: #"[{"id": 1}]"#)
),
CannedRoute(
pattern: RequestPattern(method: .post, pathRegex: "^/api/model/\\d+$"),
response: CannedResponse(string: #"{"success": true}"#)
)
])
let networkService = HTTPNetworkService(transport: transport)GentleNetworking is built around:
- β Predictability over magic
- β Protocol-driven design
- β Explicit dependency injection
- β Modern Swift concurrency
- β Testability by default
- β Small surface area with strong guarantees
It is intentionally minimal and avoids over-abstracting or hiding networking behavior.
Portions of drafting and editorial refinement in this repository were accelerated using large language models (including ChatGPT, Claude, and Gemini) under direct human design, validation, and final approval. All technical decisions, code, and architectural conclusions are authored and verified by the repository maintainer.
MIT License Free for personal and commercial use.
Built by Jonathan Ritchey Gentle Giraffe Apps Senior iOS Engineer --- Swift | SwiftUI | Concurrency