0
点赞
收藏
分享

微信扫一扫

Siwft网络请求-Alamofire

是归人不是过客 2022-04-16 阅读 19
swiftios

一个简单的get请求接口为例,搭建一个比较完整的网络框架,其中包括Session管理,路由和错误处理

这里以获取用户文章列表接口为例

创建模型

/// 实用泛行实现通用格式
public struct ResponseData<ResultData>: Codable where ResultData: Codable {
    let code: String
    let message: String
    let data: ResultData
}

public struct Post: Codable {
    let id: Int
    let title: String
    let body: String
    let userID: Int
}

extension Post {
    enum CodingKeys: String, CodingKey {
        case id
        case title
        case body
        case userID = "userId" //自定义key
    }
}

创建路由PostsApiRouter

常量配置

里面包含baseURL, 参数key, header常用字段定义,或者其他…

import Foundation

/// baseURL
struct ProductionServer {
    #if DEBUG
    static let baseURL = "http://localhost:3000/"
    #else
    static let baseURL = "https://jsonplaceholder.typicode.com/"
    #endif
}

/// 定义传参key值
struct APIParameterKey {
    static let password = "password"
    static let email    = "email"
    static let userId   = "userId"
}

/// httpheader
enum HTTPHeaderField: String {
    case authentication = "Authorization"
    case contentType = "Content-Type"
    case acceptType = "Accept"
    case acceptEncoding = "Accept-Encoding"
}

/// 传输类型
enum ContentType: String {
    case json = "application/json"
}

路由PostsApiRouter
import Alamofire

/// 文章类路由
enum PostsApiRouter{
    case getPosts(userId: Int)
}

extension PostsApiRouter: APIConfiguration {
    /// 请求方法
    var method: HTTPMethod {
        switch self {
        case .getPosts:
            return .get
        }
    }
    
    ///请求路径
    var path: String {
        switch self {
        case .getPosts:
            return "posts"
        }
    }
    
    /// 请求参数
    var parameters: Parameters? {
        switch self {
        case .getPosts(let userId):
            return [APIParameterKey.userId : userId]
        }
    }
    
    //重写encoding
    var encoding: ParameterEncoding {
        switch method {
        case .get:
            return URLEncoding.default
        default:
            return JSONEncoding.default
        }
    }
    
    ///构建request
    func asURLRequest() throws -> URLRequest {
        let url = try ProductionServer.baseURL.asURL()
        
        var urlRequest = URLRequest(url: url.appendingPathComponent(path))
        
        //http method
        urlRequest.httpMethod = method.rawValue
        
        // header:
        urlRequest.setValue(ContentType.json.rawValue, forHTTPHeaderField: HTTPHeaderField.acceptType.rawValue)
        urlRequest.setValue(ContentType.json.rawValue, forHTTPHeaderField: HTTPHeaderField.contentType.rawValue)
        
        do {
            urlRequest = try encoding.encode(urlRequest, with: parameters)
        } catch  {
            throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
        }

        return urlRequest
        
    }
}

创建请求管理类

import Foundation
import RxSwift
import Alamofire
import PromiseKit

public final class AFApiProvider {
    
    private static var _apiProvider = AFApiProvider(session: {() -> Session in
        ///自定义session
        let sessionConfig = URLSessionConfiguration.default
        sessionConfig.headers = .default
        let serverTrustManager = AFTrustManager() //自定义服务器信任管理器
        let interceptor = AFApiRequestInterceptor() //自定义/拦截器,根据需求自行添加
        
        return Session(configuration: sessionConfig,
                       interceptor: interceptor,
                       serverTrustManager: serverTrustManager,
                       redirectHandler: nil, //重定向操作
            cachedResponseHandler: nil, //缓存
            eventMonitors: []) //事件监听
    }())
    
    
    public static func shared() -> AFApiProvider {
        return _apiProvider
    }
    
    private let _disposeBag = DisposeBag()
    
    private var _session: Session
    
    private init(session: Session = Session.default) {
        _session = session
    }

}
自定义RequestInterceptor
import Alamofire

/// 请求拦截器
public final class AFApiRequestInterceptor: RequestInterceptor {
    
    /// 定义准备请求闭包,可以回调在这个时候做某些事
    private let _prepare: ((URLRequest) -> URLRequest)?
    /// 将要发送请求闭包
    private let _willSend: ((URLRequest) -> Void)?
    
    init(prepare:((URLRequest) -> URLRequest)? = nil, willSend: ((URLRequest) -> Void)? = nil) {
        _prepare = prepare
        _willSend = willSend
    }
    
    public func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (AFResult<URLRequest>) -> Void) {
        // 准备请求, 然后对request做一些设置,比如这里设置header
        var request = _prepare?(urlRequest) ?? urlRequest
        request.setValue("zh-cn", forHTTPHeaderField: "Accept-Language")
        request.setValue("gzip, deflate", forHTTPHeaderField: "Accept-Encoding")
        //设置完成后将要发送
        _willSend?(request)
        completion(.success(request))
    }
    
    public func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
        completion(.doNotRetry)
    }
}
ServerTrustManager

服务器证书信任管理器ServerTrustManager,这里只是陪着不做验证,如果项目需要验证证书的可以写在这个管理器里

import Alamofire

/// 服务器证书信任管理,这里不做验证,信任所有
public final class AFTrustManager: ServerTrustManager {
    init() {
        let allHostsMustBeEvaluated = false
        let evaluators = ["": DisabledEvaluator()] //不做验证
        super.init(allHostsMustBeEvaluated: allHostsMustBeEvaluated, evaluators: evaluators)
    }

错误处理,对服务器错误和服务器和客户端约定错误进行扩展

import Foundation
// 服务器报错
enum ServerError: Error {
    case forbidden              //Status code 403
    case notFound               //Status code 404
    case internalServerError    //Status code 500
}

/// 服务器错误处理
extension ServerError {
        
    public var errorDescription: String {
        switch self {
        case .forbidden:
            return "服务器禁止访问"
        case .notFound:
            return "未找到页面"
        case .internalServerError:
            return "服务器异常"
        }
    }
}

import Foundation

/// 自定义错误和服务器约定错误
public enum ServerApiError: Swift.Error {
        /// 解析Json格式错误
        case parseJsonError

        /// 解析服务器定义StatusCode格式错误
        case parseStatusCodeTypeError
        
        /// 服务器自定义错误
        case serverDefineError(code: String, message: String)
    
}
extension ServerApiError {
    
    /// 错误描述
    public var errorAPIDescription: String {
        switch self {
        case .parseJsonError:
            return "解析Json格式错误"
        case .parseStatusCodeTypeError:
            return "解析服务器定义StatusCode格式错误"
        case .serverDefineError(_, let message): //返回服务器定义的错误信息
            return message
        }
    }
}


import Alamofire

///扩展AFError描述属性
extension  AFError {
    public var AFErrorDescription: String {
        switch self {
        case .invalidURL(let urlConvertible):
            return "无效的URL,错误url: \(urlConvertible)"
        case .sessionDeinitialized:
            return """
            session无效而没有错误,因此它可能被意外取消了初始化。 \
            在请求期间,请确保保留对您的session的引用。
            """
        default:
//            return "剩余的自行定义,其实并没有什么用,Alamofire已经对其错误进行了定义`errorDescription`,不过是英文而已,也还比较好理解"
            return self.errorDescription
        }
    }
}

/// 扩展错误描述属性
extension Swift.Error {
    
    public var description: String {
        if let afError = self as? AFError {
            return afError.errorDescription
        }else if let serverError = self as? ServerError {
            return serverError.errorDescription
        }else if let serverApiError = self as? ServerApiError {
            return serverApiError.errorAPIDescription
        } else {
            return localizedDescription
        }
    }
}
	

请求封装

/// 请求管理
extension AFApiProvider {
    /// 返回Promis
    /// - Parameters
    ///     - urlConvertible: `URLRequestConvertible`请求
    ///     - observerOn:`ImmediateSchedulerType` 监听队列 默认全局对列
    ///     - subscribeOn: `ImmediateSchedulerType`回调队列 默认主队列
    /// - return: Promise<T>
    public func request<T: Codable> (_ urlConvertible: Alamofire.URLRequestConvertible,
                                     observerOn: ImmediateSchedulerType = ConcurrentDispatchQueueScheduler(queue: DispatchQueue.global()),
                                     subscribeOn: ImmediateSchedulerType = MainScheduler.instance) -> Promise<T> {
        return Promise{ seal in
            
            observerRequest(urlConvertible)
                .observeOn(observerOn)
                .subscribeOn(subscribeOn)
                .subscribe(onSuccess: { seal.fulfill($0)}) { seal.reject($0)}
                .disposed(by: _disposeBag)
        }
    }
    
    /// 发起请求,使用rxswift做回调,里面进行错误统一处理
    /// - Parameters:
    ///     - urlConvertible: `URLRequestConvertible` 构建DataRequest
    /// - return: `Single<T>`,`Single<T>` 携带返回模型或错误
    private func observerRequest<T: Codable>(_ urlConvertible: Alamofire.URLRequestConvertible) -> Single<T> {
        
        return Single<T>.create { (observer) -> Disposable in
            
            let request =  AFApiProvider.shared()._session
                .request(urlConvertible)
                .validate()
                .responseDecodable { (response: DataResponse<T> ) in
                    do {
                        let dataModel =  try response
                            ._storeServerTimerIntervalDistance()
                            ._catchRandomDomainFlag()
                            ._filterServerSuccessData()///验证服务器格式和定义的错误
                            .map(type: T.self)  //json转模型
                        observer(.success(dataModel))
                    } catch let error{
                        observer(.error(error))
                    }
            }
            
            return Disposables.create {
                
                request.cancel()
            }
        }
    }
}

extension DataResponse {
    
    /// 校验http返回 校准服务器时间,看自己需求是否需要
    fileprivate func _storeServerTimerIntervalDistance() throws -> DataResponse {
        guard let serverTime = response?.allHeaderFields["Date"] as? String else {
            return self
        }
        
        let dataFormatter = DateFormatter().then {
            $0.locale = Locale(identifier: "en_US_POSIX")
            $0.dateFormat = "EEE, d MMM yyyy HH:mm:ss zzz"
        }
        
        if let serverDate = dataFormatter.date(from: serverTime) {
            let timeIntervaDistance = Date().timeIntervalSince1970 - serverDate.timeIntervalSince1970
            print("请求时间差: \(timeIntervaDistance)")
            
        }
        return self
    }
    
    ///根据http返回处理特殊逻辑,如切换域名等,看自己需求
    fileprivate func _catchRandomDomainFlag() -> DataResponse {
        guard let statusCode = response?.statusCode else { return self }
        if [403, 502, 503].contains(statusCode) {
            print("http 错误码: \(statusCode)")
        }
        return self
    }
    
    /// 服务器返回数据格式错误,这样就必须让服务端按规范的数据格式返回数据,不然直接抛出错误
    /// 根据自己项目需求更改
    fileprivate func _filterServerSuccessData() throws -> DataResponse {
        
        do {
            let responseJson = try mapJSON(failsOnEmptyData: false)
            guard let dic = responseJson as? [String : Any] else {
                throw ServerApiError.parseJsonError
            }
            if let error = _praseServerError(dic: dic) { throw error  }
            return self
        } catch  {
            throw error
        }
    }
    
    
    /// 服务器自定义数据错误,直接抛出错误
    private func _praseServerError(dic:[String: Any]) -> Error? {
        
        //returnCode 为服务器与前端约定的returnCode
        guard let code = dic["code"] as? String else { return ServerApiError.parseStatusCodeTypeError }
        let message = dic["message"] as? String
        guard ["000000"].contains(code) else { //比如0为请求成功,那么其他状态为错误信息,返回错误
            return ServerApiError.serverDefineError(code: code, message: message ?? "服务器返回 错误 code: \(code)")
        }
        return nil
    }
    
    
}

extension DataResponse {
    
    /// 对返回结果转Json
    /// - failsOnEmptyData:默认为true, 如果失败返回空
    func mapJSON(failsOnEmptyData: Bool = true) throws -> Any {
        
        guard let `data` = data else { return NSNull() }
        do {
            return try JSONSerialization.jsonObject(with: data, options: .allowFragments)
        } catch {
            if data.count < 1 && !failsOnEmptyData {
                return NSNull()
            }
            throw AFError.responseSerializationFailed(reason: .jsonSerializationFailed(error: error))
        }
    }
    
    /// 将json转成模型,参考moya
    func map<D: Decodable>(type: D.Type, atKeyPath keyPath: String? = nil, using decoder: JSONDecoder = JSONDecoder(), failsOnEmptyData: Bool = true) throws -> D {
        let serializeToData: (Any) throws -> Data? = { (jsonObject) in
            guard JSONSerialization.isValidJSONObject(jsonObject) else {
                return nil
            }
            do {
                return try JSONSerialization.data(withJSONObject: jsonObject)
            } catch {
                throw AFError.responseSerializationFailed(reason: .jsonSerializationFailed(error: error))
            }
        }
        
        guard let `data` = data else { throw APIError.parseJsonError }
        let jsonData: Data
        
        keyPathCheck: if let keyPath = keyPath {
            guard let jsonObject = (try mapJSON(failsOnEmptyData: failsOnEmptyData) as? NSDictionary)?.value(forKeyPath: keyPath) else {
                if failsOnEmptyData {
                    throw APIError.parseJsonError
                } else {
                    jsonData = data
                    break keyPathCheck
                }
            }
            
            if let data = try serializeToData(jsonObject) {
                jsonData = data
            } else {
                let wrappedJsonObject = ["value": jsonObject]
                let wrappedJsonData: Data
                if let data = try serializeToData(wrappedJsonObject) {
                    wrappedJsonData = data
                } else {
                    throw APIError.parseJsonError
                }
                do {
                    return try decoder.decode(DecodableWrapper<D>.self, from: wrappedJsonData).value
                } catch let error {
                    throw AFError.responseSerializationFailed(reason: .jsonSerializationFailed(error: error))
                }
            }
        } else {
            jsonData = data
        }
        do {
            if jsonData.count < 1 && !failsOnEmptyData {
                if let emptyJSONObjectData = "{}".data(using: .utf8), let emptyDecodableValue = try? decoder.decode(D.self, from: emptyJSONObjectData) {
                    return emptyDecodableValue
                } else if let emptyJSONArrayData = "[{}]".data(using: .utf8), let emptyDecodableValue = try? decoder.decode(D.self, from: emptyJSONArrayData) {
                    return emptyDecodableValue
                }
            }
            return try decoder.decode(D.self, from: jsonData)
        } catch let error {
            throw AFError.responseSerializationFailed(reason: .jsonSerializationFailed(error: error))
        }
    }
}

private struct DecodableWrapper<T: Decodable>: Decodable {
    let value: T
}

也可提供一个api管理类,可有可无,方便管理

import Alamofire
import PromiseKit
import RxSwift

public final class APIClient {
    
    public static func getPosts(userId: Int) ->Promise<ResponseData<[Post]>> {
        
        return AFApiProvider.shared().request(PostsApiRouter.getPosts(userId: userId))
    }
}

最后完成请求

    @IBAction func requestClick(_ sender: Any) {
        
        APIClient.getPosts(userId: 101).ensure {
          ///这里可做网络菊花之类的
            print("请求中....")
        }.done { (postData) in
            print("请求完成....")
            postData.data.forEach { (post) in
                print(post)
            }
        }.catch { (error) in
                 ///错误处理
            print(error.description)
        }
    }
举报

相关推荐

0 条评论