iOS端基于moya封装网络请求框架设计

373 阅读3分钟

为了在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)")
    }
}

四、高级特性扩展

  1. 多环境切换:通过修改NetworkTargetTypebaseURL动态切换环境
  2. 请求重试机制:通过RxSwift的retry操作符实现
  3. 文件上传/下载:扩展Moya的Task类型
  4. Mock测试数据:使用Moya的sampleData返回测试数据

swift

// Mock示例
extension UserAPI {
    var sampleData: Data {
        switch self {
        case .login:
            return """
            { "code": 200, "token": "mock_token" }
            """.data(using: .utf8)!
        }
    }
}

五、设计原则总结

  1. 单一职责:每个模块只处理特定任务(插件/缓存/错误)
  2. 开闭原则:通过协议扩展增加新功能,避免修改原有代码
  3. 类型安全:利用Swift的泛型和关联类型保证接口安全
  4. 可测试性:所有组件支持单元测试,依赖注入方便
  5. 可观察性:完善的日志系统和错误监控

通过这种设计,开发者可以快速构建健壮的网络层,同时保持代码的整洁和可维护性。