Swift之Moya使用和封裝

Moya是什么?

Moya是對請求庫Alamofire的抽象封裝,相當(dāng)于OC中YTKNetwork和AFNetworking的關(guān)系。

為什么用Moya?

我們用Moya在Github上的一張圖來解釋。

image.png

TargetType

TargetType這個是使用moya必須要實現(xiàn)的一個協(xié)議,先看一下它有哪些定義

/// The protocol used to define the specifications necessary for a `MoyaProvider`.
public protocol TargetType {

    /// The target's base `URL`.
    var baseURL: URL { get }

    /// The path to be appended to `baseURL` to form the full `URL`.
    var path: String { get }

    /// The HTTP method used in the request.
    var method: Moya.Method { get }

    /// Provides stub data for use in testing. Default is `Data()`.
    var sampleData: Data { get }

    /// The type of HTTP task to be performed.
    var task: Task { get }

    /// The type of validation to perform on the request. Default is `.none`.
    var validationType: ValidationType { get }

    /// The headers to be used in the request.
    var headers: [String: String]? { get }
}

看完之后我們來定義一個結(jié)構(gòu)體,遵守TargetType協(xié)議

import Foundation
import Moya

// NetworkAPI就是一個遵循TargetType協(xié)議的枚舉
enum NetworkAPI {
    //測試天氣
    case realtimeWeather(cityId:String)
    
}

extension NetworkAPI: TargetType{
    var baseURL: URL {
        return URL(string: "https://go.apipost.cn/")!
    }
    
    var path: String {
        switch self {
        case .realtimeWeather:
            return "?Query=test"
            
        }
    }
    
    var method: Moya.Method {
        switch self {
        case .realtimeWeather:
            return .post
        }
    }
    
    var task: Moya.Task {
        // 公共參數(shù)
        var params: [String: Any] = [:]
//        params["token"] = "Gz1qYLXeBW8MZuUfDlr9wsAYuVS1cZFMJY9BbaF842L2gRps747o4w=="
        
        switch self {
        case .realtimeWeather(let cityId):
            params["cityId"] = cityId
        }
        return .requestParameters(parameters: params, encoding: JSONEncoding.default)
    }
    
    var headers: [String : String]? {
        var headers: [String: String] = [:]
        
        switch self {
        case .realtimeWeather:
            headers["Content-type"] = "application/json;charset=utf-8"
        }
        
        return headers
    }
    
}

這里只說一下task,它是一個枚舉,這里看一下moya內(nèi)部定義,我們可以根據(jù)需要進(jìn)行創(chuàng)建

/// Represents an HTTP task.
public enum Task {

    /// A request with no additional data.
    case requestPlain

    /// A requests body set with data.
    case requestData(Data)

    /// A request body set with `Encodable` type
    case requestJSONEncodable(Encodable)

    /// A request body set with `Encodable` type and custom encoder
    case requestCustomJSONEncodable(Encodable, encoder: JSONEncoder)

    /// A requests body set with encoded parameters.
    case requestParameters(parameters: [String: Any], encoding: ParameterEncoding)

    /// A requests body set with data, combined with url parameters.
    case requestCompositeData(bodyData: Data, urlParameters: [String: Any])

    /// A requests body set with encoded parameters combined with url parameters.
    case requestCompositeParameters(bodyParameters: [String: Any], bodyEncoding: ParameterEncoding, urlParameters: [String: Any])

    /// A file upload task.
    case uploadFile(URL)

    /// A "multipart/form-data" upload task.
    case uploadMultipart([MultipartFormData])

    /// A "multipart/form-data" upload task  combined with url parameters.
    case uploadCompositeMultipart([MultipartFormData], urlParameters: [String: Any])

    /// A file download task to a destination.
    case downloadDestination(DownloadDestination)

    /// A file download task to a destination with extra parameters using the given encoding.
    case downloadParameters(parameters: [String: Any], encoding: ParameterEncoding, destination: DownloadDestination)
}

MoyaProvider

我們對網(wǎng)絡(luò)的請求基本上就是MoyaProvider來調(diào)用了,我們先看一下它的定義,具體的參數(shù)什么意思我們都可以在這個類中找到對應(yīng)的參數(shù)說明。

    /// Initializes a provider.
    public init(endpointClosure: @escaping EndpointClosure = MoyaProvider.defaultEndpointMapping,
                requestClosure: @escaping RequestClosure = MoyaProvider.defaultRequestMapping,
                stubClosure: @escaping StubClosure = MoyaProvider.neverStub,
                callbackQueue: DispatchQueue? = nil,
                session: Session = MoyaProvider<Target>.defaultAlamofireSession(),
                plugins: [PluginType] = [],
                trackInflights: Bool = false) {

        self.endpointClosure = endpointClosure
        self.requestClosure = requestClosure
        self.stubClosure = stubClosure
        self.session = session
        self.plugins = plugins
        self.trackInflights = trackInflights
        self.callbackQueue = callbackQueue
    }

Moya的使用

接下來我們就可以使用最簡單的調(diào)用方法:

        let networkProvider = MoyaProvider<NetworkAPI>()
        let target = NetworkAPI.realtimeWeather(cityId: "123")
        networkProvider.request(target) { result in
            
        }

當(dāng)然這些都只是簡單的case幫助理解,當(dāng)我們使用的時候還需要進(jìn)行二次封裝,以及進(jìn)行一些配置信息等,下面我就把我封裝好的代碼貼出來一起研究下
NetworkAPI.swift文件如下:
這里主要做一些接口的配置,也是我們經(jīng)常使用的

import Foundation
import Moya

// NetworkAPI就是一個遵循TargetType協(xié)議的枚舉
enum NetworkAPI {
    //測試天氣
    case realtimeWeather(cityId:String)
    
}

extension NetworkAPI: TargetType{
    var baseURL: URL {
        return URL(string: "https://go.apipost.cn/")!
    }
    
    var path: String {
        switch self {
        case .realtimeWeather:
            return "?Query=test"
            
        }
    }
    
    var method: Moya.Method {
        switch self {
        case .realtimeWeather:
            return .post
        }
    }
    
    var task: Moya.Task {
        // 公共參數(shù)
        var params: [String: Any] = [:]
//        params["token"] = "Gz1qYLXeBW8MZuUfDlr9wsAYuVS1cZFMJY9BbaF842L2gRps747o4w=="
        
        switch self {
        case .realtimeWeather(let cityId):
            params["cityId"] = cityId
        }
        return .requestParameters(parameters: params, encoding: JSONEncoding.default)
    }
    
    var headers: [String : String]? {
        var headers: [String: String] = [:]
        
        switch self {
        case .realtimeWeather:
            headers["Content-type"] = "application/json;charset=utf-8"
        }
        
        return headers
    }
    
}

NetworkPlugin.swift文件如下:
這里是自定義的插件,當(dāng)然你也可以使用Moya默認(rèn)的四種

import Foundation
import Moya

/*
 Moya默認(rèn)有4個插件分別為:
 AccessTokenPlugin 管理AccessToken的插件
 CredentialsPlugin 管理認(rèn)證的插件
 NetworkActivityPlugin 管理網(wǎng)絡(luò)狀態(tài)的插件
 NetworkLoggerPlugin 管理網(wǎng)絡(luò)log的插件
 */
// 插件,實現(xiàn)pluginType可以實現(xiàn)在網(wǎng)絡(luò)請求前轉(zhuǎn)菊花,請求完成結(jié)束轉(zhuǎn)菊花,或者寫日志等功能
struct NetworkPlugin: PluginType {

    /// Called to modify a request before sending.(可進(jìn)行數(shù)據(jù)加密等)
    func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
        return request
    }
    
    /// Called immediately before a request is sent over the network (or stubbed).(可進(jìn)行網(wǎng)絡(luò)等待,loading等)
    func willSend(_ request: RequestType, target: TargetType) {
        
    }

    /// Called after a response has been received, but before the MoyaProvider has invoked its completion handler.(loading結(jié)束等)
    func didReceive(_ result: Result<Response, MoyaError>, target: TargetType) {
        
    }

    /// Called to modify a result before completion.(可進(jìn)行數(shù)據(jù)解密等)
    func process(_ result: Result<Response, MoyaError>, target: TargetType) -> Result<Response, MoyaError> {
        return result
    }

}

NetworkManager.swift文件如下
這里就是我們的網(wǎng)絡(luò)請求管理中心了,一些配置信息都在這里設(shè)置

import Foundation
import Moya


/// 超時時長
private var requestTimeOut: Double = 30

//這個closure存放了一些moya進(jìn)行網(wǎng)絡(luò)請求前的一些數(shù)據(jù),可以在閉包中設(shè)置公共headers
private let endpointClosure = { (target: NetworkAPI) -> Endpoint in
     var endpoint: Endpoint = MoyaProvider.defaultEndpointMapping(for: target)
//     endpoint = endpoint.adding(newHTTPHeaderFields: ["platform": "iOS", "version" : "1.0"])
     return endpoint
 }
//這個閉包是moya提供給我們對網(wǎng)絡(luò)請求開始前最后一次機(jī)會對請求進(jìn)行修改,比如設(shè)置超時時間(默認(rèn)是60s),禁用cookie等
private let requestClosure = { (endpoint: Endpoint, done: @escaping MoyaProvider<NetworkAPI>.RequestResultClosure) -> Void in
//     guard var request = try? endpoint.urlRequest() else { return }
//     // 設(shè)置請求超時時間
//     request.timeoutInterval = 30
//     done(.success(request))
     do {
         var request = try endpoint.urlRequest()
         // 設(shè)置請求時長
         request.timeoutInterval = requestTimeOut
         // 打印請求參數(shù)
         if let requestData = request.httpBody {
             print("請求的url:\(request.url!)" + "\n" + "\(request.httpMethod ?? "")" + "發(fā)送參數(shù)" + "\(String(data: request.httpBody!, encoding: String.Encoding.utf8) ?? "")")
         } else {
             print("請求的url:\(request.url!)" + "\(String(describing: request.httpMethod))")
         }

         if let header = request.allHTTPHeaderFields {
             print("請求頭內(nèi)容\(header)")
         }

         done(.success(request))
     } catch {
         done(.failure(MoyaError.underlying(error, nil)))
     }
     
 }

private let networkProvider = MoyaProvider<NetworkAPI>(endpointClosure: endpointClosure, requestClosure: requestClosure, plugins: [NetworkPlugin()], trackInflights: false)



class NetworkManager {
    /// progress的回調(diào)
    typealias NetworkProgress = (CGFloat) -> Void
    
    static func request(_ target: NetworkAPI, progress: NetworkProgress? = nil, completion: @escaping Completion) -> Cancellable {
        
        let task = networkProvider.request(target, callbackQueue: DispatchQueue.main) { progressResponse in
            progress?(CGFloat(progressResponse.progress))
        } completion: { result in
            completion(result)
        }
        return task
        
    }
}

以上封裝還不夠徹底,因為我們并沒有對返回的數(shù)據(jù)進(jìn)行處理,所以等有時間,我會在NetworkManager做一個對返回數(shù)據(jù)進(jìn)行簡單處理成json的方法,最后在給到我們的業(yè)務(wù)層使用,先到這里吧,祝大家國慶節(jié)快樂!

10.10日更新##

Response的封裝

第一層封裝:我們返回一個NetworkResult的結(jié)構(gòu)體

import Foundation

struct NetworkResult<M> {
    
    var data: M?
    var info: String?
    var code: String
    
}

那么NetworkManager文件里面的請求接口就改為以下:

struct NetworkManager<M> {
    /// progress的回調(diào)
    typealias NetworkProgress = (CGFloat) -> Void
    /// 請求完成的回調(diào)
    typealias NetworkCompletion = (NetworkResult<M>) -> Void
    
    @discardableResult
    func request(_ target: NetworkAPI, progress: NetworkProgress? = nil, completion: @escaping NetworkCompletion) -> Cancellable {
        
        let task = networkProvider.request(target, callbackQueue: DispatchQueue.main) { progressResponse in
            progress?(CGFloat(progressResponse.progress))
        } completion: { result in
            //result轉(zhuǎn)為NetworkResult結(jié)構(gòu)體
            switch result {
            case .success(let response):
                do{
                    guard let json = try response.mapJSON() as? [String: Any] else {
                        let networkResult = NetworkResult<M>(info: "服務(wù)器返回的不是JSON數(shù)據(jù)", code: "-1")
                        completion(networkResult)
                        return
                    }
                    
                    let networkResult = NetworkResult<M>(data: json["data"] as? M, info: json["message"] as? String, code: json["code"] as? String ?? "-1")
                    completion(networkResult)
                    
                } catch {
                    let networkResult = NetworkResult<M>(info: "解析出錯:\(error.localizedDescription)", code: "-1")
                    completion(networkResult)
                }
                
            case .failure(let error):
                let networkResult = NetworkResult<M>(info: "請求失敗:\(String(describing: error.errorDescription))", code: "-1")
                completion(networkResult)
                
            }
        }
        return task
        
    }
}

第二層封裝:結(jié)合Swift4.0中的Encodable、Decodable協(xié)議,利用泛型知識將NetworkResult中的data轉(zhuǎn)為對應(yīng)的數(shù)據(jù)模型返回出去。
NetworkResult.swift文件如下

import Foundation
import Moya

struct NetworkResult<M: Decodable> {
    
    var data: M?
    var info: String?
    var code: String
    
    init(json: [String: Any]) {
        code = json["code"] as? String ?? "-1"
        info = json["message"] as? String
//        data = parseData(jsonObj: json["data"])
        data = parseData(jsonObj: json["data"])
    }
    
    init(errorMsg: String?) {
        code = "-1"
        info = errorMsg
    }
    
    /// 解析數(shù)據(jù)
    func parseData(jsonObj: Any?) -> M? {
        /// 判斷是否為nil
        guard let dataObj = jsonObj else {
            return nil
        }
        
        /// 判斷是否為NSNull
        guard !(dataObj as AnyObject).isEqual(NSNull()) else {
            return nil
        }
        
        /// 本身為M類型,直接賦值
        if let dataObj = dataObj as? M {
            return dataObj
        }
        
        /// 轉(zhuǎn)模型
        let jsonData = try? JSONSerialization.data(withJSONObject: dataObj, options: .prettyPrinted)
        guard let data = jsonData else { return nil }
        do{
            return try JSONDecoder().decode(M.self, from: data)
        } catch {
            print(error)
            return nil
        }
    }
    
}

NetworkManager.swift文件請求接口如下:

struct NetworkManager<M: Codable> {
    /// progress的回調(diào)
    typealias NetworkProgress = (CGFloat) -> Void
    /// 請求完成的回調(diào)
    typealias NetworkCompletion = (NetworkResult<M>) -> Void
    
    @discardableResult
    func request(_ target: NetworkAPI, progress: NetworkProgress? = nil, completion: @escaping NetworkCompletion) -> Cancellable {
        
        let task = networkProvider.request(target, callbackQueue: DispatchQueue.main) { progressResponse in
            progress?(CGFloat(progressResponse.progress))
        } completion: { result in
            //result轉(zhuǎn)為NetworkResult結(jié)構(gòu)體
            switch result {
            case .success(let response):
                do{
                    guard let json = try response.mapJSON() as? [String: Any] else {
                        completion(NetworkResult<M>(errorMsg: "服務(wù)器返回的不是JSON數(shù)據(jù)"))
                        return
                    }
                    
                    completion(NetworkResult<M>(json: json))
                    
                } catch {
                    //解析出錯
                    completion(NetworkResult<M>(errorMsg:error.localizedDescription))
                }
                
            case .failure(let error):
                //請求出錯
                completion(NetworkResult<M>(errorMsg: error.errorDescription))
                
            }
        }
        return task
        
    }
}

當(dāng)然,我們也可以給Result再增加一個擴(kuò)展方法,優(yōu)化我們的NetworkManager文件,以下就是最終的文件了
NetworkResult

import Foundation
import Moya

struct NetworkResult<M: Decodable> {
    
    var data: M?
    var info: String?
    var code: String
    
    init(json: [String: Any]) {
        code = json["code"] as? String ?? "-1"
        info = json["message"] as? String
//        data = parseData(jsonObj: json["data"])
        data = parseData(jsonObj: json["data"])
    }
    
    init(errorMsg: String?) {
        code = "-1"
        info = errorMsg
    }
    
    /// 解析數(shù)據(jù)
    func parseData(jsonObj: Any?) -> M? {
        /// 判斷是否為nil
        guard let dataObj = jsonObj else {
            return nil
        }
        
        /// 判斷是否為NSNull
        guard !(dataObj as AnyObject).isEqual(NSNull()) else {
            return nil
        }
        
        /// 本身為M類型,直接賦值
        if let dataObj = dataObj as? M {
            return dataObj
        }
        
        /// 轉(zhuǎn)模型
        let jsonData = try? JSONSerialization.data(withJSONObject: dataObj, options: .prettyPrinted)
        guard let data = jsonData else { return nil }
        do{
            return try JSONDecoder().decode(M.self, from: data)
        } catch {
            print(error)
            return nil
        }
    }
    
}
//給Result增加一個擴(kuò)展方法
extension Result where Success: Response, Failure == MoyaError {
    func mapNetworkResult<M>(_ type: M.Type) -> NetworkResult<M> where M: Decodable {
        switch self {
        case .success(let response):
            do {
                guard let json = try response.mapJSON() as? [String: Any] else {
                    /// 不是JSON數(shù)據(jù)
                    return NetworkResult(errorMsg: "服務(wù)器返回的不是JSON數(shù)據(jù)")
                }
                return NetworkResult(json: json)
            } catch {
                /// 解析出錯
                return NetworkResult(errorMsg: error.localizedDescription)
            }
        case .failure(let error):
            /// 請求出錯
            return NetworkResult(errorMsg: error.errorDescription)
        }
    }
    
}

NetworkManager

import Foundation
import Moya


/// 超時時長
private var requestTimeOut: Double = 30

//這個closure存放了一些moya進(jìn)行網(wǎng)絡(luò)請求前的一些數(shù)據(jù),可以在閉包中設(shè)置公共headers
private let endpointClosure = { (target: NetworkAPI) -> Endpoint in
     var endpoint: Endpoint = MoyaProvider.defaultEndpointMapping(for: target)
//     endpoint = endpoint.adding(newHTTPHeaderFields: ["platform": "iOS", "version" : "1.0"])
     return endpoint
 }
//這個閉包是moya提供給我們對網(wǎng)絡(luò)請求開始前最后一次機(jī)會對請求進(jìn)行修改,比如設(shè)置超時時間(默認(rèn)是60s),禁用cookie等
private let requestClosure = { (endpoint: Endpoint, done: @escaping MoyaProvider<NetworkAPI>.RequestResultClosure) -> Void in
//     guard var request = try? endpoint.urlRequest() else { return }
//     // 設(shè)置請求超時時間
//     request.timeoutInterval = 30
//     done(.success(request))
     do {
         var request = try endpoint.urlRequest()
         // 設(shè)置請求時長
         request.timeoutInterval = requestTimeOut
         // 打印請求參數(shù)
         if let requestData = request.httpBody {
             print("請求的url:\(request.url!)" + "\n" + "\(request.httpMethod ?? "")" + "發(fā)送參數(shù)" + "\(String(data: request.httpBody!, encoding: String.Encoding.utf8) ?? "")")
         } else {
             print("請求的url:\(request.url!)" + "\(String(describing: request.httpMethod))")
         }

         if let header = request.allHTTPHeaderFields {
             print("請求頭內(nèi)容\(header)")
         }

         done(.success(request))
     } catch {
         done(.failure(MoyaError.underlying(error, nil)))
     }
     
 }

private let networkProvider = MoyaProvider<NetworkAPI>(endpointClosure: endpointClosure, requestClosure: requestClosure, plugins: [NetworkPlugin()], trackInflights: false)


struct NetworkManager<M: Codable> {
    /// progress的回調(diào)
    typealias NetworkProgress = (CGFloat) -> Void
    /// 請求完成的回調(diào)
    typealias NetworkCompletion = (NetworkResult<M>) -> Void
    
    @discardableResult
    func request(_ target: NetworkAPI, progress: NetworkProgress? = nil, completion: @escaping NetworkCompletion) -> Cancellable {
        
        let task = networkProvider.request(target, callbackQueue: DispatchQueue.main) { progressResponse in
            progress?(CGFloat(progressResponse.progress))
        } completion: { result in
            //result轉(zhuǎn)為NetworkResult結(jié)構(gòu)體
            let networkResult = result.mapNetworkResult(M.self)
            completion(networkResult)
        }
        return task
        
    }
}

使用起來就簡單多了,比如我們來個用戶登錄:
先來創(chuàng)建個和服務(wù)器商量好的結(jié)構(gòu)體模型

import Foundation

struct LoginModel: Codable  {
    
    var token: String
    var user: User
}

struct User: Codable {
    
    var id: String
    var mobile: String
    var userName: String?
    var url: String?
    
    init(id: String, mobile: String) {
        self.id = id
        self.mobile = mobile
    }
}

然后我們配置NetworkApi文件(這個就不寫了),接下來就是調(diào)用:

        let target = NetworkAPI.login(mobile: "12345789", verifyCode: "6666")
        NetworkManager<LoginModel>().request(target){ [weak self] (result) in
            guard let self = self else { return }
            if let loginModel = result.data {
                print("登錄成功!")
                let userMessage = "token: \(loginModel.token)" + "\n\nid: \(loginModel.user.id)" + "\n\nmobile: \(loginModel.user.mobile)"
               print(userMessage)
            } else {
                //失敗
                print(result.info)
            }
            
        }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容