English | 简体中文
Moira is a lightweight networking layer built for Swift Concurrency. It keeps request construction explicit and composable without forcing application architecture choices.
Moira carries a personal tribute to Moya.
I learned a lot from its API modeling ideas, but over time the maintenance burden, concurrency-era gaps, and growing integration friction made it hard to continue relying on it in modern projects.
Moira is the practical answer to that transition:
- Keep the networking layer thin and replaceable
- Preserve semantic API description and composability
- Embrace async/await-native request flow and clear lifecycle extension points
Design background: 「iOS」网络层工程范式迁移
- Request description:
APIRequest+RequestPayload - Predictable build rules:
RequestBuilder - Pluggable lifecycle: Transform / Observer / Retry / ShortCircuit
- Default decoding via
ResponseDecoder - Upload/download progress via
RequestTask<APIResponse>.progress
import Moira
enum UserAPI: APIRequest {
case profile(id: String)
case search(query: String)
case updateProfile(id: String, payload: UpdateProfile)
var path: String {
switch self {
case .profile(let id):
return "/users/\(id)"
case .search:
return "/users/search"
case .updateProfile(let id, _):
return "/users/\(id)"
}
}
var method: RequestMethod {
switch self {
case .profile, .search:
return .get
case .updateProfile:
return .patch
}
}
var payload: RequestPayload {
switch self {
case .profile:
return RequestPayload()
case .search(let query):
return RequestPayload().appendingQueryItem(URLQueryItem(name: "q", value: query))
case .updateProfile(_, let body):
return RequestPayload().withJSON(body)
}
}
}
struct UpdateProfile: Encodable, Sendable {
let name: String
}
let baseURL = URL(string: "https://api.example.com")!
let provider = APIProvider(
client: AlamofireClient(),
builder: RequestBuilder(baseURL: baseURL)
)
let user: User = try await provider.request(UserAPI.profile(id: "123"))let queryPayload = RequestPayload()
.appendingQueryItems([URLQueryItem(name: "page", value: "1")])
let formPayload = RequestPayload()
.withURLEncodedForm([URLQueryItem(name: "q", value: "swift")])
let dataPayload = RequestPayload()
.withData(Data("raw".utf8))let response = try await provider.requestResponse(UserAPI.profile(id: "123"))
print(response.statusCode)
print(response.data)let request = UploadAPI.data(Data("payload".utf8))
let task = try await provider.requestTask(request)
if let progress = task.progress {
Task {
for await update in progress {
print(update.completedBytes)
}
}
}
let response = try await task.response()
print(response.statusCode)Moira ships with built-in documentation for both Chinese and English readers.
Index:
- English docs index:
Docs/index.md - Chinese docs index:
Docs/zh/index.md
Core docs:
- Getting Started:
Docs/Getting-Started.md/Docs/zh/Getting-Started.md - Core Types:
Docs/Core-Types.md/Docs/zh/Core-Types.md - Plugins:
Docs/Plugins.md/Docs/zh/Plugins.md - Request Building:
Docs/Request-Building.md/Docs/zh/Request-Building.md - Clients:
Docs/Clients.md/Docs/zh/Clients.md - Architecture:
Docs/Architecture.md/Docs/zh/Architecture.md
swift build
swift test