iOS 使用Moya網路請求

海神Lewis發表於2018-07-24

Moya最新版本11.0.2

由於前段時間寫了這篇文章,最新Moya已更新最新版本,故此也更新了下用法,本人已使用,故特意奉上最新的使用demo供參考。 Moya11.0.2Demo

Moya簡介

Moya 是你的 app 中缺失的網路層。不用再去想在哪兒(或者如何)安放網路請求,Moya 替你管理。

Moya 有幾個比較好的特性:

  • 編譯時檢查正確的API端點訪問.

  • 使你定義不同端點列舉值對應相應的用途更加明晰.

  • 提高測試地位從而使單元測試更加容易.

Swift我們用Alamofire來做網路庫.而Moya在Alamofire的基礎上又封裝了一層,如下流程圖說明Moya的簡單工作流程圖:

簡單流程圖

Moya的官方下載地址點我強大的Moya,有具體的使用方法在demo裡面有說明。

本文主要介紹一下Moya的用法

  • 設定請求頭部資訊
  • 設定超時時間
  • 自定義外掛
  • 自簽名證書

注意:以下所出現的NetAPIManager跟官網上demo的** GitHub**是一樣型別的檔案,都是這個enum實現一個協議TargetType,點進去可以看到TargetType定義了我們傳送一個網路請求所需要的東西,什麼baseURL,parameter,method等一些計算性屬性,我們要做的就是去實現這些東西,當然有帶預設值的我們可以不去實現,但是設定頭部資訊跟超時時間就要修改這些系統預設設定了。

為了看得更加清楚,貼上NetAPIManager檔案的內容

//
//  NetAPIManager.swift
//  NN110
//
//  Created by 陳亦海 on 2017/5/12.
//  Copyright © 2017年 陳亦海. All rights reserved.
//

import Foundation
import Moya


enum NetAPIManager {
    case Show
    case upload(bodyData: Data)
    case download
    case request(isTouch: Bool, body: Dictionary<String, Any>? ,isShow: Bool)
}


extension NetAPIManager: TargetType {
    var baseURL: URL {//伺服器地址
        
        switch self {
        case .request( _, _, _):
            return URL(string: "https://www.pmphmall.com")!
        default:
            return URL(string: "https://httpbin.org")!
        }
        
        
    }
    
    var path: String {//具體某個方法的路徑
        switch self {
        case .Show:
            return ""
        case .upload(_):
            return ""
        case .request(_, _, _):
            return "/app/json.do"
        case .download:
            return ""
        }
    }
    
    var method: Moya.Method {//請求的方法 get或者post之類的
        switch self {
        case .Show:
            return .get
        case .request(_, _, _):
            return .post
        default:
            return .post
        }
    }
    
    var parameters: [String: Any]? {//請求的get post給伺服器的引數
        switch self {
        case .Show:
            return nil
        case .request(_, _, _):
            return ["msg":"H4sIAAAAAAAAA11SSZJFIQi7EqPAEgTvf6TP62W7sMoSQhKSWDrs6ZUKVWogLwYV7RjHFBZJlNlzloN6LVqID4a+puxqRdUKVNLwE1TRcZIC/fjF2rPotuXmb84r1gMXbiASZIZbhQdKEewJlz41znDkujCHuQU3dU7G4/PmVRnwArMLXukBv0J23XVahNO3VX35wlgce6TLUzzgPQJFuHngAczl6VhaNXpmRLxJBlMml6gdLWiXxTdO7I+iEyC7XuTirCQXOk4dotgArgkH/InxVjfNTnE/uY46++hyAiLFuFL4cv1Z8WH5DgB2GnvFXMh5gm53Tr13vqqrEYtcdXfkNsMwKB+9sAQ77grNJmquFWOhfXA/DELlMB0KKFtHOc/ronj1ml+Z7qas82L3VWiCVQ+HEitjTVzoFw8RisFN/jJxBY4awvq427McXqnyrfCsl7oeEU6wYgW9yJtj1lOkx0ELL5Fw4z071NaVzRA9ebxWXkFyothgbB445cpRmTC+//F73r1kOyQ3lTpec12XNDR00nnq5/YmJItW3+w1z27lSOLqgVctrxG4xdL9WVPdkH1tkiZ/pUKBGhADAAA="]
        default:
            return nil
        
        }
    }
    
    var sampleData: Data { //編碼轉義
       return "{}".data(using: String.Encoding.utf8)!
    }
    
    var task: Task { //一個請求任務事件
        
        switch self {

        
        case let .upload(data):
        return .upload(.multipart([MultipartFormData(provider: .data(data), name: "file", fileName: "gif.gif", mimeType: "image/gif")]))
            
        default:
            return .request

       }

     }
    
    var parameterEncoding: ParameterEncoding {//編碼的格式
        switch self {
        case .request(_, _, _):
            return URLEncoding.default
        default:
            return URLEncoding.default
        }
        
    }
    //以下兩個引數是我自己寫,用來控制網路載入的時候是否允許操作,跟是否要顯示載入提示,這兩個引數在自定義外掛的時候會用到
    var touch: Bool { //是否可以操作
        
        switch self {
        case .request(let isTouch, _, _):
            return isTouch
        default:
            return false
        }
        
    }
    
    var show: Bool { //是否顯示轉圈提示
        
        switch self {
        case .request( _, _,let isShow):
            return isShow
        default:
            return false
        }
        
    }
    
    
}

複製程式碼

如何設定Moya請求頭部資訊

頭部資訊的設定在開發過程中很重要,如伺服器生成的token,使用者唯一標識等 我們直接上程式碼,不說那麼多理論的東西,哈哈

// MARK: - 設定請求頭部資訊
let myEndpointClosure = { (target: NetAPIManager) -> Endpoint<NetAPIManager> in
    
    
    let url = target.baseURL.appendingPathComponent(target.path).absoluteString
    let endpoint = Endpoint<NetAPIManager>(
        url: url,
        sampleResponseClosure: { .networkResponse(200, target.sampleData) },
        method: target.method,
        parameters: target.parameters,
        parameterEncoding: target.parameterEncoding
    )

    //在這裡設定你的HTTP頭部資訊
    return endpoint.adding(newHTTPHeaderFields: [
        "Content-Type" : "application/x-www-form-urlencoded",
        "ECP-COOKIE" : ""
        ])
    
}
複製程式碼

如何設定請求超時時間

// MARK: - 設定請求超時時間
let requestClosure = { (endpoint: Endpoint<NetAPIManager>, done: @escaping MoyaProvider<NetAPIManager>.RequestResultClosure) in
    
    guard var request = endpoint.urlRequest else { return }
    
    request.timeoutInterval = 30    //設定請求超時時間
    done(.success(request))
}
複製程式碼

自定義外掛

自定義外掛必須PluginType協議的兩個方法willSend與didReceive

//
//  MyNetworkActivityPlugin.swift
//  NN110
//
//  Created by 陳亦海 on 2017/5/10.
//  Copyright © 2017年 CocoaPods. All rights reserved.
//

import Foundation
import Result
import Moya


/// Network activity change notification type.
public enum MyNetworkActivityChangeType {
    case began, ended
}

/// Notify a request's network activity changes (request begins or ends).
public final class MyNetworkActivityPlugin: PluginType {
    
    
    
    public typealias MyNetworkActivityClosure = (_ change: MyNetworkActivityChangeType, _ target: TargetType) -> Void
    let myNetworkActivityClosure: MyNetworkActivityClosure
    
    public init(newNetworkActivityClosure: @escaping MyNetworkActivityClosure) {
        self.myNetworkActivityClosure = newNetworkActivityClosure
    }
    
    // MARK: Plugin
    
    /// Called by the provider as soon as the request is about to start
    public func willSend(_ request: RequestType, target: TargetType) {
        myNetworkActivityClosure(.began,target)
    }
    
    /// Called by the provider as soon as a response arrives, even if the request is cancelled.
    public func didReceive(_ result: Result<Moya.Response, MoyaError>, target: TargetType) {
        myNetworkActivityClosure(.ended,target)
    }
}

複製程式碼

使用自定義外掛方法

// MARK: - 自定義的網路提示請求外掛
let myNetworkPlugin = MyNetworkActivityPlugin { (state,target) in
    if state == .began {
        //        SwiftSpinner.show("Connecting...")
        
        let api = target as! NetAPIManager
        if api.show {
            print("我可以在這裡寫載入提示")
        }
        
        if !api.touch {
            print("我可以在這裡寫禁止使用者操作,等待請求結束")
        }

        print("我開始請求\(api.touch)")
        
        UIApplication.shared.isNetworkActivityIndicatorVisible = true
    } else {
        //        SwiftSpinner.show("request finish...")
        //        SwiftSpinner.hide()
        print("我結束請求")
        UIApplication.shared.isNetworkActivityIndicatorVisible = false
        
    }
}

複製程式碼

自簽名證書

在16年的WWDC中,Apple已表示將從2017年1月1日起,**所有新提交的App必須強制性應用HTTPS協議來進行網路請求。**預設情況下非HTTPS的網路訪問是禁止的並且不能再通過簡單粗暴的向Info.plist中新增NSAllowsArbitraryLoads 設定繞過ATS(App Transport Security)的限制(否則須在應用稽核時進行說明並很可能會被拒)。所以還未進行相應配置的公司需要儘快將升級為HTTPS的事項提上程式了。本文將簡述HTTPS及配置數字證書的原理並以配置例項和出現的問題進行說明,希望能對你提供幫助。(比心~)

iOS 使用Moya網路請求
HTTPS: 簡單來說,HTTPS就是HTTP協議上再加一層加密處理的SSL協議,即HTTP安全版。相比HTTP,HTTPS可以保證內容在傳輸過程中不會被第三方檢視、及時發現被第三方篡改的傳輸內容、防止身份冒充,從而更有效的保證網路資料的安全。 HTTPS客戶端與伺服器互動過程: 1、 客戶端第一次請求時,伺服器會返回一個包含公鑰的數字證書給客戶端; 2、 客戶端生成對稱加密金鑰並用其得到的公鑰對其加密後返回給伺服器; 3、 伺服器使用自己私鑰對收到的加密資料解密,得到對稱加密金鑰並儲存; 4、 然後雙方通過對稱加密的資料進行傳輸。
iOS 使用Moya網路請求
數字證書: 在HTTPS客戶端與伺服器第一次互動時,服務端返回給客戶端的數字證書是讓客戶端驗證這個數字證書是不是服務端的,證書所有者是不是該伺服器,確保資料由正確的服務端發來,沒有被第三方篡改。數字證書可以保證數字證書裡的公鑰確實是這個證書的所有者(Subject)的,或者證書可以用來確認對方身份。證書由公鑰、證書主題(Subject)、數字簽名(digital signature)等內容組成。其中數字簽名就是證書的防偽標籤,目前使用最廣泛的SHA-RSA加密。 證書一般分為兩種:

  1. 一種是向權威認證機構購買的證書,服務端使用該種證書時,因為蘋果系統內建了其受信任的簽名根證書,所以客戶端不需額外的配置。為了證書安全,在證書釋出機構公佈證書時,證書的指紋演算法都會加密後再和證書放到一起公佈以防止他人偽造數字證書。而證書機構使用自己的私鑰對其指紋演算法加密,可以用內建在作業系統裡的機構簽名根證書來解密,以此保證證書的安全。
  2. 另一種是自己製作的證書,即自簽名證書。好處是不需要花錢購2買,但使用這種證書是不會受信任的,所以需要我們在程式碼中將該證書配置為信任證書.

自簽名證書具體實現: 我們在使用自簽名證書來實現HTTPS請求時,因為不像機構頒發的證書一樣其簽名根證書在系統中已經內建了,所以我們需要在App中內建自己伺服器的簽名根證書來驗證數字證書。首先將服務端生成的.cer格式的根證書新增到專案中,注意在新增證書要一定要記得勾選要新增的targets。這裡有個地方要注意:蘋果的ATS要求服務端必須支援TLS 1.2或以上版本;必須使用支援前向保密的密碼;證書必須使用SHA-256或者更好的簽名hash演算法來簽名,如果證書無效,則會導致連線失敗。由於我在生成的根證書時簽名hash演算法低於其要求,在配置完請求時一直報NSURLErrorServerCertificateUntrusted = -1202錯誤,希望大家可以注意到這一點。

那麼如何在Moya中使用自簽名的證書來實現HTTPS網路請求呢,請期待下回我專門分享......需要自定義一個Manager管理

綜合使用的方法如下

定義一個公用的Moya請求服務物件

let MyAPIProvider = MoyaProvider<NetAPIManager>(endpointClosure: myEndpointClosure,requestClosure: requestClosure, plugins: [NetworkLoggerPlugin(verbose: true, responseDataFormatter: JSONResponseDataFormatter),myNetworkPlugin])

// MARK: -建立一個Moya請求
func sendRequest(_ postDict: Dictionary<String, Any>? = nil,
                 success:@escaping (Dictionary<String, Any>)->(),
                 failure:@escaping (MoyaError)->()) -> Cancellable? {
    
   let request = MyAPIProvider.request(.Show) { result in    
        switch result {
        case let .success(moyaResponse):
            
            
            do {
                let any = try moyaResponse.mapJSON()
                let data =  moyaResponse.data
                let statusCode =  moyaResponse.statusCode
                MyLog("\(data) --- \(statusCode) ----- \(any)")
                
                success(["":""])
                

            } catch {
                
            }
            
           
            
        case let .failure(error):
            
            print(error)
            failure(error)
        }
    }
    
    return request
}
複製程式碼

取消所有的Moya請求

// MARK: -取消所有請求
func cancelAllRequest() {
//    MyAPIProvider.manager.session.invalidateAndCancel()  //取消所有請求
    MyAPIProvider.manager.session.getTasksWithCompletionHandler { dataTasks, uploadTasks, downloadTasks in
        dataTasks.forEach { $0.cancel() }
        uploadTasks.forEach { $0.cancel() }
        downloadTasks.forEach { $0.cancel() }
    }
    
    //let sessionManager = Alamofire.SessionManager.default
    //sessionManager.session.getTasksWithCompletionHandler { dataTasks, uploadTasks, downloadTasks in
    //    dataTasks.forEach { $0.cancel() }
    //    uploadTasks.forEach { $0.cancel() }
    //    downloadTasks.forEach { $0.cancel() }
    //}

}
複製程式碼

完畢,待續更高階的用法...

本人的簡書上也有發表 Lewis簡書 有寫的不對的地方,請指正。

相關文章