A unified networking architecture built on top of Moya, Alamofire, SwiftyJSON, 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
You can run the rename script from the repository root:
swift Scripts/rename.swift MyAppPreview the changes first with:
swift Scripts/rename.swift MyApp --dry-runCurrent package requirements:
- Swift 6
- iOS 13+
- macOS 10.15+
- Swift Package Manager
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
│ │ │── NetworkReachabilityManager.swift
│ │ └── Retry.swift
│ ├── Plugins
│ │ │── NetworkLoggerPlugin.swift
│ │ │── TimeoutPlugin.swift
│ │ └── TimerLoggerPlugin.swift
│ └── #XXXAPIProvider.swift
│ └── XXXNetworkModel
│ └── Models
└── 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
- Default dynamic JSON decoding through SwiftyJSON
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
The current package supports iOS 13+ and macOS 10.15+, so the async request wrapper uses an NSLock-based LockIsolated helper to protect cancellable state.
If your project only supports iOS 16+ and macOS 13+, you can use Swift's OSAllocatedUnfairLock directly instead.
All backend responses are normalized into a standard format:
{
"code": 0,
"message": "success",
"data": {},
"request_id": "xxx"
}HTTP Response (status 200)
↓
BaseResponse<T>
↓
Business Code Validation (code == 0)
↓
Data Extraction
↓
Decodable Model
In this implementation, HTTP status must be 200, business success code is 0, and unknown business codes are wrapped as XXXNetworkWrappedError.
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
@testable import XXXNetworkModel
@Suite
struct UserTest {
@Test
func info() async throws {
let user = try await XXXAPIProvider.shared.request(XXXAPI.User.info, to: 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?,
"request_id": String
}This ensures:
- Unified parsing logic
- Centralized error handling
- Backend contract consistency
In the current implementation, code == 0 means success. Other known codes should be added to XXXNetworkServerError and kept in sync with backend definitions.
- 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.Article.delete(ids: [1, 2, 3]))If the backend omits data, the default JSON response becomes JSON.null.
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)
- TimeoutPlugin for custom request timeout behavior
- Retry and polling helpers for async workflows
- Custom error mapping
- Alternative decoding strategies
- Multi-target routing
This repository is meant to be copied, renamed, and adapted to your own backend contract.
Recommended steps:
- Rename
XXXNetworkKit,XXXAPI, and error domains to your app or company prefix. - Replace sample APIs such as
XXXAPI.UserandXXXAPI.Articlewith your own business domains. - Update
BaseResponse<T>inResponse.mapResult(to:)to match your backend envelope. - Replace
XXXNetworkServerErrorwith your backend business error codes. - Decide whether
XXXNetworkModelshould stay as a separate target or move into your app modules. - Keep the test-driven API workflow: every new API should have a focused integration test.
After these steps, this project becomes your app's networking foundation rather than an external generic dependency.
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