为了在iOS端基于Moya设计一个高可用、易维护的网络请求框架,以下是分步骤的详细设计方案,包含代码示例和关键设计思路:
一、基础架构分层
采用 Moya + RxSwift/Combine + Codable 技术栈,分层结构如下:
Network Layer
├── Config // 全局配置
├── Core // 核心请求封装
├── Plugin // 插件(日志/加载状态)
├── Cache // 缓存策略
├── Error // 错误处理
└── API // 业务接口定义
二、核心实现步骤
1. 全局配置(Config)
swift
protocol NetworkTargetType: TargetType {
var publicParams: [String: Any]? { get } // 全局公共参数
}
extension NetworkTargetType {
var baseURL: URL { URL(string: "https://api.example.com")! }
var headers: [String: String]? { ["Content-Type": "application/json"] }
var publicParams: [String: Any]? { ["version": "1.0.0"] }
// 自动合并公共参数
var task: Task {
guard let publicParams = publicParams else { return .requestPlain }
switch task {
case .requestParameters(var params, let encoding):
params.merge(publicParams) { (current, _) in current }
return .requestParameters(parameters: params, encoding: encoding)
default:
return .requestParameters(parameters: publicParams, encoding: URLEncoding.default)
}
}
}
2. 业务API定义(API)
swift
enum UserAPI {
case login(phone: String, password: String)
case getUserProfile(id: Int)
}
extension UserAPI: NetworkTargetType {
var path: String {
switch self {
case .login: return "/auth/login"
case .getUserProfile: return "/user/profile"
}
}
var method: Moya.Method {
switch self {
case .login: return .post
default: return .get
}
}
var task: Task {
switch self {
case let .login(phone, password):
return .requestParameters(parameters: ["phone": phone, "pwd": password], encoding: JSONEncoding.default)
case let .getUserProfile(id):
return .requestParameters(parameters: ["id": id], encoding: URLEncoding.queryString)
}
}
}
3. 核心请求封装(Core)
swift
class NetworkManager<T: NetworkTargetType> {
private let provider: MoyaProvider<T>
init(plugins: [PluginType] = [NetworkLoggerPlugin()]) {
provider = MoyaProvider(plugins: plugins)
}
// 支持RxSwift/Observable
func rxRequest(_ target: T) -> Observable<Response> {
return provider.rx.request(target)
.filterSuccessfulStatusCodes()
.asObservable()
.catchErrorMap { error in
throw NetworkError.moyaError(error)
}
}
// 支持Swift Concurrency
func asyncRequest(_ target: T) async throws -> Response {
try await withCheckedThrowingContinuation { continuation in
provider.request(target) { result in
switch result {
case .success(let response):
continuation.resume(returning: response)
case .failure(let error):
continuation.resume(throwing: NetworkError.moyaError(error))
}
}
}
}
}
4. 统一错误处理(Error)
swift
enum NetworkError: Error {
case invalidURL
case requestTimeout
case serverError(code: Int, message: String)
case dataParsingFailed
case moyaError(MoyaError)
var localizedDescription: String {
switch self {
case .requestTimeout: return "请求超时"
case .dataParsingFailed: return "数据解析失败"
case let .serverError(_, msg): return msg
// ... 其他错误描述
}
}
}
// 错误解析扩展
extension Response {
func mapServerError() throws -> Response {
guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let code = json["code"] as? Int, code != 200 else {
return self
}
let message = json["message"] as? String ?? "Unknown Error"
throw NetworkError.serverError(code: code, message: message)
}
}
5. 拦截器插件(Plugin)
swift
// 认证Token插件
final class AuthPlugin: PluginType {
func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
var request = request
if let token = KeychainService.shared.token {
request.addValue("Bearer (token)", forHTTPHeaderField: "Authorization")
}
return request
}
}
// 加载状态插件
final class LoadingPlugin: PluginType {
func willSend(_ request: RequestType, target: TargetType) {
DispatchQueue.main.async {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
}
}
func didReceive(_ result: Result<Response, MoyaError>, target: TargetType) {
DispatchQueue.main.async {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}
}
}
6. 缓存策略(Cache)
swift
struct NetworkCache {
static let shared = NSCache<NSString, AnyObject>()
static func cacheKey(_ target: TargetType) -> String {
return target.baseURL.absoluteString + target.path + "(target.task)"
}
static func save(_ data: Data, forKey key: String) {
shared.setObject(data as AnyObject, forKey: key as NSString)
}
static func load(forKey key: String) -> Data? {
return shared.object(forKey: key as NSString) as? Data
}
}
// 在请求时优先读取缓存
extension NetworkManager {
func requestWithCache(_ target: T) -> Observable<Response> {
let cacheKey = NetworkCache.cacheKey(target)
if let cachedData = NetworkCache.load(forKey: cacheKey) {
return Observable.just(Response(statusCode: 200, data: cachedData))
} else {
return rxRequest(target).do(onNext: { response in
NetworkCache.save(response.data, forKey: cacheKey)
})
}
}
}
三、使用示例
swift
// 1. 发起登录请求
let networkManager = NetworkManager<UserAPI>()
networkManager.rxRequest(.login(phone: "13800138000", password: "123456"))
.mapServerError()
.mapJSON()
.subscribe(onNext: { json in
print("登录成功: (json)")
}, onError: { error in
print("登录失败: (error.localizedDescription)")
})
.disposed(by: disposeBag)
// 2. 使用Swift Concurrency
Task {
do {
let response = try await networkManager.asyncRequest(.getUserProfile(id: 1001))
let user = try JSONDecoder().decode(User.self, from: response.data)
print("用户信息: (user)")
} catch {
print("请求失败: (error)")
}
}
四、高级特性扩展
- 多环境切换:通过修改
NetworkTargetType
的baseURL
动态切换环境 - 请求重试机制:通过RxSwift的
retry
操作符实现 - 文件上传/下载:扩展Moya的
Task
类型 - Mock测试数据:使用Moya的
sampleData
返回测试数据
swift
// Mock示例
extension UserAPI {
var sampleData: Data {
switch self {
case .login:
return """
{ "code": 200, "token": "mock_token" }
""".data(using: .utf8)!
}
}
}
五、设计原则总结
- 单一职责:每个模块只处理特定任务(插件/缓存/错误)
- 开闭原则:通过协议扩展增加新功能,避免修改原有代码
- 类型安全:利用Swift的泛型和关联类型保证接口安全
- 可测试性:所有组件支持单元测试,依赖注入方便
- 可观察性:完善的日志系统和错误监控
通过这种设计,开发者可以快速构建健壮的网络层,同时保持代码的整洁和可维护性。