15. Alamofire + Moya 网络模板
这份模板覆盖:接口定义、统一错误、鉴权注入、日志、重试。代码可直接拆到项目中使用。
一、安装(SPM)
https://github.com/Alamofire/Alamofire
https://github.com/Moya/Moya
二、统一响应与错误
import Foundation
struct BaseResponse<T: Decodable>: Decodable {
let code: Int
let message: String
let data: T?
}
enum APIError: Error {
case invalidResponse
case httpStatus(Int)
case server(code: Int, message: String)
case decoding(Error)
case network(Error)
}
struct Empty: Decodable {}
三、接口定义(TargetType)
import Moya
enum API {
case login(username: String, password: String)
case posts
}
extension API: TargetType {
var baseURL: URL { URL(string: "https://api.example.com")! }
var path: String {
switch self {
case .login: return "/login"
case .posts: return "/posts"
}
}
var method: Moya.Method {
switch self {
case .login: return .post
case .posts: return .get
}
}
var task: Task {
switch self {
case let .login(username, password):
let params = ["username": username, "password": password]
return .requestParameters(parameters: params, encoding: JSONEncoding.default)
case .posts:
return .requestPlain
}
}
var headers: [String : String]? {
["Accept": "application/json", "Content-Type": "application/json"]
}
var sampleData: Data { Data() }
}
四、Token 管理与插件
import Moya
final class TokenStorage {
static let shared = TokenStorage()
private init() {}
var token: String? = nil
}
let tokenPlugin = AccessTokenPlugin { _ in
TokenStorage.shared.token ?? ""
}
let loggerPlugin = NetworkLoggerPlugin(configuration: .init(logOptions: [.requestHeaders, .requestBody, .successResponseBody]))
五、Moya Provider(Alamofire Session)
import Alamofire
import Moya
final class NetworkProvider {
static let shared = NetworkProvider()
let provider: MoyaProvider<API>
private init() {
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 15
let session = Session(configuration: configuration)
provider = MoyaProvider<API>(session: session, plugins: [tokenPlugin, loggerPlugin])
}
}
六、统一请求封装
import Moya
final class APIService {
private let provider = NetworkProvider.shared.provider
func request<T: Decodable>(_ target: API, type: T.Type, completion: @escaping (Result<T, APIError>) -> Void) {
provider.request(target) { result in
switch result {
case .success(let response):
guard (200..<300).contains(response.statusCode) else {
completion(.failure(.httpStatus(response.statusCode)))
return
}
do {
let base = try JSONDecoder().decode(BaseResponse<T>.self, from: response.data)
if base.code == 0, let data = base.data {
completion(.success(data))
} else {
completion(.failure(.server(code: base.code, message: base.message)))
}
} catch {
completion(.failure(.decoding(error)))
}
case .failure(let error):
completion(.failure(.network(error)))
}
}
}
}
七、重试包装
final class RetryService {
private let service = APIService()
func request<T: Decodable>(_ target: API, type: T.Type, retry: Int, completion: @escaping (Result<T, APIError>) -> Void) {
service.request(target, type: T.self) { result in
switch result {
case .success:
completion(result)
case .failure(let error):
if retry > 0, case .network = error {
self.request(target, type: T.self, retry: retry - 1, completion: completion)
} else {
completion(result)
}
}
}
}
}
八、调用示例
struct Post: Decodable {
let id: Int
let title: String
}
let api = RetryService()
api.request(.posts, type: [Post].self, retry: 2) { result in
switch result {
case .success(let list):
print(list.count)
case .failure(let error):
print(error)
}
}
以上模板已经覆盖常规项目的网络层需求,直接拆分到项目即可使用。