swift——項(xiàng)目的基礎(chǔ)架構(gòu)之網(wǎng)絡(luò)篇—Moya

最近要做新項(xiàng)目疲吸,原來(lái)的項(xiàng)目框架老了也該升級(jí)了座每。了解了下Moya,發(fā)現(xiàn)異常的好用摘悴。很好的把接口峭梳,接口所需參數(shù),網(wǎng)絡(luò)請(qǐng)求時(shí)的自定義設(shè)置等集中到了一起。修改和增加都非常清晰葱椭。

導(dǎo)入就不說(shuō)了捂寿,貼上Github網(wǎng)址——Moya

Moya的基本思想

我們希望一些網(wǎng)絡(luò)抽象層能夠直接充分地封裝實(shí)際調(diào)用Alamofire。這應(yīng)該是簡(jiǎn)單的孵运,常見的事情是容易的秦陋,但足夠全面,復(fù)雜的事情也很容易治笨。

如果你使用Alamofire來(lái)抽象出來(lái)URLSession驳概,為什么不用東西去抽象出URL,參數(shù)等等呢旷赖?

Moya的一些很棒的功能:

  • 編譯時(shí)檢查正確的API端點(diǎn)訪問(wèn)顺又。
  • 讓您定義具有相關(guān)枚舉值的不同端點(diǎn)并且清晰的使用。
  • 將測(cè)試樁作為一流公民杠愧,使單元測(cè)試非常簡(jiǎn)單待榔。

使用Moya

首先創(chuàng)建一個(gè)枚舉來(lái)存放你所有的API

//API 參數(shù)列表
enum bsAPI {
    case getRequest
    case Login(phoneNumber: String, password: String)
    case testNetwork
}

對(duì)你的枚舉(bsAPI)進(jìn)行擴(kuò)展,并遵循TargetType協(xié)議流济。這里面會(huì)把請(qǐng)求的網(wǎng)址锐锣,路徑參數(shù),基本參數(shù)绳瘟,請(qǐng)求類型等等全部定義好雕憔。

extension bsAPI: TargetType {
    
    //網(wǎng)址
    var baseURL: URL{
        return URL.init(string: Using_URL)!
    }
    
    //內(nèi)容拼接
    var path:String{
        return ""
    }
    
    var method: Moya.Method{
        switch self {
        case .getRequest:
            return .get
        default:
            return .post
        }
    }
    
    var parameters: [String: Any]?{
        
        switch self {
        case .Login(let phoneNumber, let password):
            let words = EncodeAction(dic: ["phoneNumber":phoneNumber, "password":password], key: FUNC_Login.md5())  /// 這是我的加密方法,自己Demo就隨便一個(gè)字典就好
            return ["func":"", "words":words]
            
        case .testNetwork:
            let words: String = EncodeAction(dic: Dictionary<String, Any>(), key: FUNC_Test.md5())  /// 這是我的加密方法糖声,自己Demo就隨便一個(gè)字典就好
            return ["words":words]
            
        default:
            return nil
        }
    }
    
    var parameterEncoding: ParameterEncoding {
        return URLEncoding.default
    }
    
    /// Provides stub data for use in testing.
    var sampleData: Data {
        return "".data(using: .utf8)!
    }
    
    /// The type of HTTP task to be performed.
    var task: Task {
        return .request
    }
    
    /// Whether or not to perform Alamofire validation. Defaults to `false`.
    var validate: Bool {
        return false
    }
}

協(xié)議定義好了之后使用就異常簡(jiǎn)單斤彼, 定義一個(gè)類型就可以開始使用了。

let provider = MoyaProvider<bsAPI>()
 provider.request(.Show) { result in
            // do something with result
 }

但是這是最基礎(chǔ)的用法蘸泻,MoyaProvider是提供很多屬性的琉苇,下面的寫法就為你的網(wǎng)絡(luò)請(qǐng)求添加了請(qǐng)求時(shí)狀態(tài)欄轉(zhuǎn)圈(狀態(tài)欄菊花加載)和網(wǎng)絡(luò)請(qǐng)求頁(yè)面菊花加載。這樣就不用每個(gè)請(qǐng)求的時(shí)候都帶上MBProgressHUD的使用了悦施。

public init(endpointClosure: @escaping EndpointClosure = MoyaProvider.defaultEndpointMapping,
                requestClosure: @escaping RequestClosure = MoyaProvider.defaultRequestMapping,
                stubClosure: @escaping StubClosure = MoyaProvider.neverStub,
                manager: Manager = MoyaProvider<Target>.defaultAlamofireManager(),
                plugins: [PluginType] = [],
                trackInflights: Bool = false) {

        self.endpointClosure = endpointClosure
        self.requestClosure = requestClosure
        self.stubClosure = stubClosure
        self.manager = manager
        self.plugins = plugins
        self.trackInflights = trackInflights
    }

這是MoyaProvider的默認(rèn)初始化方法并扇,我們會(huì)對(duì)requestClosure和plugins這兩個(gè)屬性作出自定義。

requestClosure的自定義

let requestTimeoutClosure = { (endpoint: Endpoint<bsAPI>, done: @escaping MoyaProvider<bsAPI>.RequestResultClosure) in
    
    guard var request = endpoint.urlRequest else { return }
    request.timeoutInterval = 10    //設(shè)置請(qǐng)求超時(shí)時(shí)間
    done(.success(request))
    
}

plugins的自定義

let networkPlugin = NetworkActivityPlugin { (type) in
    switch type {
    case .began:
        /// 狀態(tài)欄轉(zhuǎn)圈
        UIApplication.shared.isNetworkActivityIndicatorVisible = true
        
    case .ended:
        /// 狀態(tài)欄停止轉(zhuǎn)圈
        UIApplication.shared.isNetworkActivityIndicatorVisible = false
    }
}

這里用到的是Moya的網(wǎng)絡(luò)監(jiān)控插件抡诞,NetworkActivityPlugin穷蛹,繼承自PluginType。定義了狀態(tài)欄的網(wǎng)絡(luò)加載昼汗。下面要自定義一個(gè)繼承自PluginType肴熏,網(wǎng)絡(luò)請(qǐng)求時(shí)頁(yè)面加載的插件。

class RequestLoadingPlugin: PluginType {
    
    var HUD:MBProgressHUD = MBProgressHUD.init()
    
    func willSend(_ request: RequestType, target: TargetType) {
        print("開始請(qǐng)求")
        
        if let keyViewController = UIApplication.shared.keyWindow?.rootViewController {
            
            HUD.mode = MBProgressHUDMode.indeterminate
            HUD.bezelView.color = UIColor.lightGray
            HUD.removeFromSuperViewOnHide = true
            HUD.backgroundView.style = .solidColor 
            HUD = MBProgressHUD.showAdded(to: keyViewController.view, animated: true)
            
        }
    }
    
    func didReceive(_ result: Result<Response, MoyaError>, target: TargetType) {
        print("結(jié)束請(qǐng)求")
        HUD.hide(animated: true, afterDelay: 0)

        guard case Result.failure(_) = result else {
            return
        }
        /// 請(qǐng)求失敗
        let errorReason: String = (result.error?.errorDescription)!
        print("請(qǐng)求失敗: \(errorReason)")
        /// 沒網(wǎng) "The Internet connection appears to be offline."
        /// 連接不到服務(wù)器 "Could not connect to the server."
        var tip = "請(qǐng)求失敗!"
        if errorReason.contains("The Internet connection appears to be offline") {
            tip = "網(wǎng)絡(luò)不給力,請(qǐng)檢查您的網(wǎng)絡(luò)"
        }
        if errorReason.contains("Could not connect to the server") {
            tip = "無(wú)法連接服務(wù)器"
        }
        /// 使用tip文字 進(jìn)行提示
    }
}

然后初始化MoyaProvider的時(shí)候帶上自定義的插件

let bsProvider = MoyaProvider<bsAPI>(requestClosure: requestTimeoutClosure , plugins: [RequestLoadingPlugin(), networkPlugin])

大功告成G曛稀M芾簟!
然后去簡(jiǎn)單使用下

bsProvider.request(bsAPI.testNetwork) { (result) in
            switch result{
            case let .success(res):
                print("======\(res)======")
                let responseDict = JSON(res.data)  ///這里是導(dǎo)入了SwiftyJSON
                print(responseDict)
                
            case let .failure(error):
                print("=====\(error)=========")
                
            }
        }

我是把API的定義和Manager分開了,在bsNetworkAPI文件里寫測(cè)試地址/正式地址鸦做,接口名璧疗。這樣子要找接口,改接口一目了然馁龟。

項(xiàng)目截圖

bsNetworkAPI文件代碼大致如下

import UIKit

#if DEBUG
let Using_URL = "" //測(cè)試地址
#else
let Using_URL = "" //正式地址
#endif

/// 登陸
let FUNC_Login = "login"

/// 測(cè)試
let FUNC_Test = "test"

bsNetworkManager里的代碼就是上文里面拆開講的那部分,給大家一個(gè)合集漆魔,這樣更方便學(xué)習(xí)坷檩。我就比較喜歡全部的代碼都給我,然后我在去自己拆解來(lái)看改抡。

import UIKit
import Moya
import Result
import MBProgressHUD
import SwiftyJSON

//API 參數(shù)列表
enum bsAPI {
    
    case getRequest
    case Login(phoneNumber: String, password: String)
    case testNetwork
    
}

let networkPlugin = NetworkActivityPlugin { (type) in
    switch type {
    case .began:
        /// 狀態(tài)欄轉(zhuǎn)圈
        UIApplication.shared.isNetworkActivityIndicatorVisible = true
        
    case .ended:
        /// 狀態(tài)欄停止轉(zhuǎn)圈
        UIApplication.shared.isNetworkActivityIndicatorVisible = false
    }
}

let requestTimeoutClosure = { (endpoint: Endpoint<bsAPI>, done: @escaping MoyaProvider<bsAPI>.RequestResultClosure) in
    
    guard var request = endpoint.urlRequest else { return }
    request.timeoutInterval = 10    //設(shè)置請(qǐng)求超時(shí)時(shí)間
    done(.success(request))
    
}

class RequestLoadingPlugin: PluginType {
    
    var HUD:MBProgressHUD = MBProgressHUD.init()
    
    func willSend(_ request: RequestType, target: TargetType) {
        print("開始請(qǐng)求")
        
        if let keyViewController = UIApplication.shared.keyWindow?.rootViewController {
            
            HUD.mode = MBProgressHUDMode.indeterminate
            HUD.bezelView.color = UIColor.lightGray
            HUD.removeFromSuperViewOnHide = true
            HUD.backgroundView.style = .solidColor
            HUD = MBProgressHUD.showAdded(to: keyViewController.view, animated: true)
            
        }
    }
    
    func didReceive(_ result: Result<Response, MoyaError>, target: TargetType) {
        print("結(jié)束請(qǐng)求")
        HUD.hide(animated: true, afterDelay: 0)

        /// 請(qǐng)求失敗
        let errorReason: String = (result.error?.errorDescription)!
        print("請(qǐng)求失敗: \(errorReason)")
        /// 沒網(wǎng) "The Internet connection appears to be offline."
        /// 連接不到服務(wù)器 "Could not connect to the server."
        var tip = "請(qǐng)求失敗!"
        if errorReason.contains("The Internet connection appears to be offline") {
            tip = "網(wǎng)絡(luò)不給力,請(qǐng)檢查您的網(wǎng)絡(luò)"
        }
        if errorReason.contains("Could not connect to the server") {
            tip = "無(wú)法連接服務(wù)器"
        }
        /// 使用tip文字 進(jìn)行提示
    }
}

let bsProvider = MoyaProvider<bsAPI>(requestClosure: requestTimeoutClosure , plugins: [RequestLoadingPlugin(), networkPlugin])

extension bsAPI: TargetType {
    
    //網(wǎng)址
    var baseURL: URL{
        return URL.init(string: Using_URL)!
    }
    
    //內(nèi)容拼接
    var path:String{
        return FUNC_Test
    }
    
    var method: Moya.Method{
        switch self {
        case .getRequest:
            return .get
        default:
            return .post
        }
    }
    
    var parameters: [String: Any]?{
        
        switch self {
        case .Login(let phoneNumber, let password):
            let words = EncodeAction(dic: ["phoneNumber":phoneNumber, "password":password], key: FUNC_Login.md5())
            return ["func":"", "words":words]
            
        case .testNetwork:
            let words: String = EncodeAction(dic: Dictionary<String, Any>(), key: FUNC_Test.md5())
            return ["words":words]
            
        default:
            return nil
        }
    }
    
    var parameterEncoding: ParameterEncoding {
        return URLEncoding.default
    }
    
    /// Provides stub data for use in testing.
    var sampleData: Data {
        return "".data(using: .utf8)!
    }
    
    /// The type of HTTP task to be performed.
    var task: Task {
        return .request
    }
    
    /// Whether or not to perform Alamofire validation. Defaults to `false`.
    var validate: Bool {
        return false
    }
}

還有疑問(wèn)可以去Github看看官方教程或下載官方例子——Moya

如果有對(duì)MBProgressHUD的那部分有疑問(wèn)的矢炼,可以去iOS版 - MBProgressHUD設(shè)置背景方框?yàn)橥该?/a>

轉(zhuǎn)載請(qǐng)注明出處,謝謝阿纤。
  • 序言:七十年代末句灌,一起剝皮案震驚了整個(gè)濱河市欠拾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌藐窄,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件荆忍,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡刹枉,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門微宝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人芥吟,你說(shuō)我怎么就攤上這事≈油遥” “怎么了钉稍?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵贡未,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我俊卤,道長(zhǎng)嫩挤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任消恍,我火速辦了婚禮岂昭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘狠怨。我一直安慰自己约啊,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布佣赖。 她就那樣靜靜地躺著恰矩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪憎蛤。 梳的紋絲不亂的頭發(fā)上外傅,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音俩檬,去河邊找鬼萎胰。 笑死,一個(gè)胖子當(dāng)著我的面吹牛棚辽,可吹牛的內(nèi)容都是我干的奥洼。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼晚胡,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼灵奖!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起估盘,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤瓷患,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后遣妥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體擅编,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年箫踩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了爱态。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡境钟,死狀恐怖锦担,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情慨削,我是刑警寧澤洞渔,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布套媚,位于F島的核電站,受9級(jí)特大地震影響磁椒,放射性物質(zhì)發(fā)生泄漏堤瘤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一浆熔、第九天 我趴在偏房一處隱蔽的房頂上張望本辐。 院中可真熱鬧医增,春花似錦、人聲如沸调窍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)洁闰。三九已至,卻和暖如春扑眉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背聘裁。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工衡便, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留洋访,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓呆抑,卻偏偏與公主長(zhǎng)得像理肺,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子妹萨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,162評(píng)論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理熏兄,服務(wù)發(fā)現(xiàn)树姨,斷路器,智...
    卡卡羅2017閱讀 134,659評(píng)論 18 139
  • 緣份真是個(gè)奇妙的東西硝清,從陌生到熟悉转晰,一切都那么順其自然水到渠成。就像我們倆注定會(huì)有故事蔗崎,那么相似又那么互補(bǔ)扰藕。最喜歡...
    愛吃橙子的周周閱讀 235評(píng)論 0 1
  • 懷孕初期是在一月份,那個(gè)時(shí)候雖然嚴(yán)重害喜未桥,卻很想吃酸酸的青桔子芥备。那種酸和檸檬的酸澀不一樣,入口一咬淆党,頓時(shí)酸味溢...
    無(wú)夢(mèng)樓緣緣堂閱讀 324評(píng)論 0 2
  • 人生就如旅行染乌,有人視自己為糞土,羨慕著別人的既可以朝九晚五荷憋,又能夠浪跡天涯褐望,而不知自己一直在和精彩擦肩而過(guò)串前。...
    M沐梵閱讀 338評(píng)論 0 1