A unified networking architecture built on top of Moya, Alamofire, and Swift Concurrency, designed as a reference implementation for standardizing API response structures, error handling, and request flow in iOS applications.
A Chinese version of this document can be found here.
The name XXXNetworkKit is a placeholder.
Replace XXX with your project or company prefix:
XXXNetworkKit β MyAppNetworkKit / ABCNetworkKit
XXXNetworkKit is a layered networking architecture designed to solve common problems in large-scale iOS applications:
-
Inconsistent API response formats across backend services
-
Fragmented error handling strategies
-
Bridging callback-based networking into Swift async/await
-
Mixing of raw JSON parsing and strongly-typed models
-
Tight coupling between business logic and networking layer
The framework provides a unified, predictable, and extensible networking layer.
β Goal:
Build a unified, predictable, and scalable networking layer.
.
βββ Package.swift
βββ README.md
βββ Sources
β βββ XXXNetworkKit
β βββ API
β β βββ XXXAPI.swift
β β βββ XXXAPI+User.swift
β β βββ XXXAPI+Article.swift
β βββ Errors
β β βββ XXXNetworkError.swift
β β βββ XXXNetworkMoyaError.swift
β β βββ XXXNetworkServerError.swift
β β βββ XXXNetworkWrappedError.swift
β βββ Helper
β β βββ Helper.swift
β βββ Plugins
β β βββ NetworkLoggerPlugin.swift
β β βββ NetworkLoggerPlugin.swift
β βββ #XXXAPIProvider.swift
βββ Tests
βββ XXXNetworkKitTests
βββ XXXNetworkKitTests.swift
XXXAPIProvider is the single entry point for all network requests.
- Unified request interface
- Async/await bridging
- Request cancellation handling
- Plugin system (logging, timing, debugging)
- Session configuration management
- Singleton shared instance
- Custom URLSession configuration
- Debug-only logging plugins
- Swift Concurrency support
The framework wraps Moya's callback-based API into Swift structured concurrency.
- withCheckedThrowingContinuation
- withTaskCancellationHandler
- Thread-safe cancellable management
- Cooperative cancellation
- Safe continuation handling
- Unified response abstraction
All backend responses are normalized into a standard format:
{
"code": 200,
"message": "success",
"data": {},
"request_id": "xxx"
}HTTP Response (status 200)
β
BaseResponse<T>
β
Business Code Validation
β
Data Extraction
β
Decodable Model
To support large-scale APIs, the framework introduces namespace-based API organization.
A single XXXAPI enum becomes:
- Hard to maintain
- Difficult to navigate
- High merge conflict risk
Group APIs by domain:
enum XXXAPI {
enum User {
case info
case login
case logout
}
enum Order {
case list
case detail(id: String)
}
}- π¦ Clear domain separation
- π Better code completion experience
- π§© Easier modularization
- π₯ Reduced team conflicts
All networking errors conform to a unified protocol:
- Server errors
- Transport errors
- Wrapped custom errors
| Layer | Type of Error |
|---|---|
| HTTP Layer | Network / Transport errors |
| Business | Server-defined error codes |
| Fallback | Wrapped custom errors |
Error
βββ XXXNetworkError (All Network Error)
βββ XXXNetworkMoyaError (Transport errors)
βββ XXXNetworkServerError (Server-defined error codes)
βββ XXXNetworkWrappedError (Wrapped custom errors)
API integration is now test-driven.
In most cases, developers are reluctant to write test cases unless it is strictly required. Therefore, this framework adopts an approach where test cases are written during the API integration process.
This not only speeds up integrationβsince there is no need to launch the entire app each time, and individual test methods can be run insteadβbut also ensures that test cases are naturally completed by the end of the integration.
Writing test cases is no longer an extra burden, but becomes a standard part of the workflow. Moreover, when adding new APIs later, running all tests can also help uncover issues in previously implemented backend interfaces.
Instead of writing API calls inside business logic:
π Write test cases first to complete API integration
import Testing
@testable import XXXNetworkKit
@Suite
struct UserTest {
@Test
func info() async throws {
let user = try await XXXAPIProvider.shared.request(XXXAPI.User.info, to: Model.User.self)
#expect(user.openid != nil)
}
@Test
func union_id() async throws {
let result = try await XXXAPIProvider.shared.request(XXXAPI.User.union_id)
#expect(result["union_id"].string != nil)
}
}- β No UI dependency
- β Faster debugging
- β Early backend validation
- β Strong type-safety verification
Define API (Namespace)
β
Write Test Case
β
Complete API Integration
β
Develop Business Logic / UI
- Full async/await support
- Task cancellation propagation
- Continuation-safe bridging
All backend responses must follow:
{
"code": Int,
"message": String,
"data": Any?
}This ensures:
- Unified parsing logic
- Centralized error handling
- Backend contract consistency
- High performance
- Type-safe
- Production-ready
- Flexible parsing
- Dynamic use cases
Dynamic JSON parsing is supported but not recommended for heavy workloads.
Codable is significantly 40x faster and preferred for large or deeply nested responses.
struct User: Codable {
let name: String
let openid: String
}
let user = try await XXXAPIProvider.shared.request(
XXXAPI.User.info,
to: User.self
)
print(user.name)let userJSON = try await XXXAPIProvider.shared.request(XXXAPI.User.info)
print(userJSON["name"].stringValue)try await XXXAPIProvider.shared.request(XXXAPI.User.delete)Follow a progression where errors are handled from the smallest scope to the largest, and adhere to a flow where errors are initiated and ultimately handled at the upper layers. Avoid scattering error-handling logic throughout the entire process.
do {
try await XXXAPIProvider.shared.request(...)
print("Request Done")
} catch XXXNetworkServerError.serverException {
// Handle a specific business error, usually used to show user-facing messages
showToast(error.localizedDescription)
} catch let error as XXXNetworkError where error.isNetworkConnectError {
// Use 'where' clause to filter network connection related errors
showToast("Network connection error")
} catch let error as XXXNetworkError {
// Handle all networking module errors
print("code: \(error.errorCode)")
} catch {
// Handle non-network errors
print("Unknown error: \(error.localizedDescription)")
}The framework supports:
- Custom plugins (logging / metrics / tracing)
- Custom error mapping
- Alternative decoding strategies
- Multi-target routing
XXXNetworkKit is a production-ready networking architecture that:
- Unifies networking via a single API provider
- Standardizes backend response format
- Centralizes error handling
- Supports async/await concurrency model
- Provides scalable architecture for large iOS applications