精选文章

15. Alamofire + Moya 网络模板

2019-12-06 · Alamofire

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)
    }
}

以上模板已经覆盖常规项目的网络层需求,直接拆分到项目即可使用。

JJ

作者简介

专注于内容创作、产品策略与设计实践。欢迎交流合作。

上一篇 下一篇