造輪子 | 如何設計一個面向協議的 iOS 網絡請求庫

2017-02-12 | MmoaaY | iOS

概述

最近開源了一個面向協議設計的網絡請求庫 MBNetwork,基於 AlamofireObjectMapper 實現,目的是簡化業務層的網絡請求操做。git

須要幹些啥

對於大部分 App 而言,業務層作一次網絡請求一般關心的問題有以下幾個:github

  • 如何在任意位置發起網絡請求。
  • 表單建立。包含請求地址、請求方式(GET/POST/……)、請求頭等……
  • 加載遮罩。目的是阻塞 UI 交互,同時告知用戶操做正在進行。好比提交表單時在提交按鈕上顯示 「菊花」,同時使其失效。
  • 加載進度展現。下載上傳圖片等資源時提示用戶當前進度。
  • 斷點續傳。下載上傳圖片等資源發生錯誤時能夠在以前已完成部分的基礎上繼續操做,這個 Alamofire 能夠支持。
  • 數據解析。由於目前主流服務端和客戶端數據交換採用的格式是 JSON,因此咱們暫時先考慮 JSON 格式的數據解析,這個 ObjectMapper 能夠支持。
  • 出錯提示。發生業務異常時,直接顯示服務端返回的異常信息。前提是服務端異常信息足夠友好。
  • 成功提示。請求正常結束時提示用戶。
  • 網絡異常從新請求。顯示網絡異常界面,點擊以後從新發送請求。

爲何是 POP 而不是 OOP

關於 POPOOP 這兩種設計思想及其特色的文章不少,因此我就不廢話了,主要說說爲啥要用 POP 來寫 MBNetworkjson

  • 想嘗試一下一切皆協議的設計方式。因此這個庫的設計只是一次極限嘗試,並不表明這就是最完美的設計方式。
  • 若是以 OOP 的方式實現,使用者須要經過繼承的方式來得到某個類實現的功能,若是使用者還須要另外某個類實現的功能,就會很尷尬。而 POP 是經過對協議進行擴展來實現功能,使用者能夠同時遵循多個協議,輕鬆解決 OOP 的這個硬傷。
  • OOP 繼承的方式會使某些子類得到它們不須要的功能。
  • 若是由於業務的增多,須要對某些業務進行分離,OOP 的方式仍是會碰到子類不能繼承多個父類的問題,而 POP 則徹底不會,分離以後,只須要遵循分離後的多個協議便可。
  • OOP 繼承的方式入侵性比較強。
  • POP 能夠經過擴展的方式對各個協議進行默認實現,下降使用者的學習成本。
  • 同時 POP 還能讓使用者對協議作自定義的實現,保證其高度可配置性。

站在 Alamofire 的肩膀上

不少人都喜歡說 AlamofireSwift 版本的 AFNetworking,可是在我看來,AlamofireAFNetworking 更純粹。這和 Swift 語言自己的特性也是有關係的,Swift 開發者們,更喜歡寫一些輕量的框架。好比 AFNetworking 把不少 UI 相關的擴展功能都作在框架內,而 Alamofire 的作法則是放在另外的擴展庫中。好比 AlamofireImageAlamofireNetworkActivityIndicatorswift

MBNetwork 就能夠當作是 Alamofire 的一個擴展庫,因此,MBNetwork 很大程度上遵循了 Alamofire 接口的設計規範。一方面,下降了 MBNetwork 的學習成本,另外一方面,從我的角度來看,Alamofire 確實有不少特別值得借鑑的地方。bash

POP

首先固然是 POP 啦,Alamofire 大量運用了 protocol + extension 的實現方式。網絡

enum

作爲檢驗寫 Swift 姿式正確與否的重要指標,Alamofire 固然不會缺。閉包

鏈式調用

這是讓 Alamofire 成爲一個優雅的網絡框架的重要緣由之一。這一點 MBNetwork 也進行了徹底的 Copy。app

@discardableResult

Alamofire 全部帶返回值的方法前面,都會有這麼一個標籤,其實做用很簡單,由於在 Swift 中,返回值若是沒有被使用,Xcode 會產生告警信息。加上這個標籤以後,表示這個方法的返回值就算沒有被使用,也不產生告警。框架

固然還有 ObjectMapper

引入 ObjectMapper 很大一部分緣由是須要作錯誤和成功提示。由於只有解析服務端的錯誤信息節點才能知道返回結果是否正確,因此咱們引入 ObjectMapper 來作 JSON 解析。 而只作 JSON 解析的緣由是目前主流的服務端客戶端數據交互格式是 JSONide

這裏須要提到的就是另一個 Alamofire 的擴展庫 AlamofireObjectMapper,從名字就能夠看出來,這個庫就是參照 Alamofire 的 API 規範來作 ObjectMapper 作的事情。這個庫的代碼不多,但實現方式很是 Alamofire,你們能夠拜讀一下它的源碼,基本上就知道如何基於 Alamofire 作自定義數據解析了。

注:被 @Foolish 安利,正在接入 ProtoBuf 中…

一步一步來

表單建立

Alamofire 的請求有三種: requestuploaddownload,這三種請求都有相應的參數,MBNetwork 把這些參數抽象成了對應的協議,具體內容參見:MBForm.swift。這種作法有幾個優勢:

  1. 對於相似 headers 這樣的參數,通常全局都是一致的,能夠直接 extension 指定。
  2. 經過協議的名字便可知道表單的功能,簡單明確。

下面是 MBNetwork 表單協議的用法舉例:

指定全局 headers 參數:

extension MBFormable {
    public func headers() -> [String: String] {
        return ["accessToken":"xxx"];
    }
}
複製代碼

建立具體業務表單:

struct WeatherForm: MBRequestFormable {
    var city = "shanghai"

    public func parameters() -> [String: Any] {
        return ["city": city]
    }

    var url = "https://raw.githubusercontent.com/tristanhimmelman/AlamofireObjectMapper/2ee8f34d21e8febfdefb2b3a403f18a43818d70a/sample_keypath_json"
    var method = Alamofire.HTTPMethod.get
}
複製代碼

表單協議化可能有過分設計的嫌疑,有同感的仍然可使用 Alamofire 對應的接口去作網絡請求,不影響 MBNetwork 其餘功能的使用。

基於表單請求數據

表單已經抽象成協議,如今就能夠基於表單發送網絡請求了,由於以前已經說過須要在任意位置發送網絡請求,而實現這一點的方法基本就這幾種:

  • 單例。
  • 全局方法,Alamofire 就是這麼幹的。
  • 協議擴展。

MBNetwork 採用了最後一種方法。緣由很簡單,MBNetwork 是以一切皆協議的原則設計的,因此咱們把網絡請求抽象成 MBRequestable 協議。

首先,MBRequestable 是一個空協議 。

/// Network request protocol, object conforms to this protocol can make network request
public protocol MBRequestable: class {

}
複製代碼

爲何是空協議,由於不須要遵循這個協議的對象幹啥。

而後對它作 extension,實現網絡請求相關的一系列接口:

func request(_ form: MBRequestFormable) -> DataRequest

func download(_ form: MBDownloadFormable) -> DownloadRequest

func download(_ form: MBDownloadResumeFormable) -> DownloadRequest

func upload(_ form: MBUploadDataFormable) -> UploadRequest

func upload(_ form: MBUploadFileFormable) -> UploadRequest

func upload(_ form: MBUploadStreamFormable) -> UploadRequest

func upload(_ form: MBUploadMultiFormDataFormable, completion: ((UploadRequest) -> Void)?)
複製代碼

這些就是網絡請求的接口,參數是各類表單協議,接口內部調用的實際上是 Alamofire 對應的接口。注意它們都返回了類型爲 DataRequestUploadRequest 或者 DownloadRequest 的對象,經過返回值咱們能夠繼續調用其餘方法。

到這裏 MBRequestable 的實現就完成了,使用方法很簡單,只須要設置類型遵循 MBRequestable 協議,就能夠在該類型內發起網絡請求。以下:

class LoadableViewController: UIViewController, MBRequestable {
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        request(WeatherForm())
    }
}
複製代碼

加載

對於加載咱們關心的點有以下幾個:

  • 加載開始須要幹啥。
  • 加載結束須要幹啥。
  • 是否須要顯示加載遮罩。
  • 在何處顯示遮罩。
  • 顯示遮罩的內容。

對於這幾點,我對協議的劃分是這樣的:

  • MBContainable 協議。遵循該協議的對象能夠作爲加載的容器。
  • MBMaskable 協議。遵循該協議的 UIView 能夠作爲加載遮罩。
  • MBLoadable 協議。遵循該協議的對象能夠定義加載的配置和流程。

MBContainable

遵循這個協議的對象只須要實現下面的方法便可:

func containerView() -> UIView?
複製代碼

這個方法返回作爲遮罩容器的 UIView。作爲遮罩的 UIView 最終會被添加到 containerView 上。

不一樣類型的容器的 containerView 是不同的,下面是各類類型容器 containerView 的列表:

容器 containerView
UIViewController view
UIView self
UITableViewCell contentView
UIScrollView 最近一個不是 UIScrollViewsuperview

UIScrollView 這個地方有點特殊,由於若是直接在 UIScrollView 上添加遮罩視圖,遮罩視圖的中心點是很是難控制的,因此這裏用了一個技巧,遞歸尋找 UIScrollViewsuperview,發現不是 UIScrollView 類型的直接返回便可。代碼以下:

public override func containerView() -> UIView? {
    var next = superview
    while nil != next {
        if let _ = next as? UIScrollView {
            next = next?.superview
        } else {
            return next
        }
    }
    return nil
}
複製代碼

最後咱們對 MBContainableextension,添加一個 latestMask 方法,這個方法實現的功能很簡單,就是返回 containerView 上最新添加的、並且遵循 MBMaskable 協議的 subview

MBMaskable

協議內部只定義了一個屬性 maskId,做用是用來區分多種遮罩。

MBNetwork 內部實現了兩個遵循 MBMaskable 協議的 UIView,分別是 MBActivityIndicatorMBMaskView,其中 MBMaskView 的效果是參照 MBProgressHUD 實現,因此對於大部分場景來講,直接使用這兩個 UIView 便可。

注:MBMaskable 協議惟一的做用是與 containerView 上其它 subview 作區分。

MBLoadable

作爲加載協議的核心部分,MBLoadable 包含以下幾個部分:

  • func mask() -> MBMaskable?:遮罩視圖,可選的緣由是可能不須要遮罩。
  • func inset() -> UIEdgeInsets:遮罩視圖和容器視圖的邊距,默認值 UIEdgeInsets.zero
  • func maskContainer() -> MBContainable?:遮罩容器視圖,可選的緣由是可能不須要遮罩。
  • func begin():加載開始回調方法。
  • func end():加載結束回調方法。

而後對協議要求實現的幾個方法作默認實現:

func mask() -> MBMaskable? {
    return MBMaskView() // 默認顯示 MBProgressHUD 效果的遮罩。
}

 func inset() -> UIEdgeInsets {
    return UIEdgeInsets.zero // 默認邊距爲 0 。
}

func maskContainer() -> MBContainable? {
    return nil // 默認沒有遮罩容器。
}

func begin() {
    show() // 默認調用 show 方法。
}

func end() {
    hide() // 默認調用 hide 方法。
}
複製代碼

上述代碼中的 show 方法和 hide 方法是實現加載遮罩的核心代碼。

show 方法的內容以下:

func show() {
    if let mask = self.mask() as? UIView {
        var isHidden = false
        if let _ = self.maskContainer()?.latestMask() {
            isHidden = true
        }
        self.maskContainer()?.containerView()?.addMBSubView(mask, insets: self.inset())
        mask.isHidden = isHidden

        if let container = self.maskContainer(), let scrollView = container as? UIScrollView {
            scrollView.setContentOffset(scrollView.contentOffset, animated: false)
            scrollView.isScrollEnabled = false
        }
    }
}
複製代碼

這個方法作了下面幾件事情:

  • 判斷 mask 方法返回的是否是遵循 MBMaskable 協議的 UIView,由於若是不是 UIView,不能被添加到其它的 UIView 上。
  • 經過 MBContainable 協議上的 latestMask 方法獲取最新添加的、且遵循 MBMaskable 協議的 UIView。若是有,就把新添加的這個遮罩視圖隱藏起來,再添加到 maskContainercontainerView 上。爲何會有多個遮罩的緣由是多個網絡請求可能同時遮罩某一個 maskContainer,另外,多個遮罩不能都顯示出來,由於有的遮罩可能有半透明部分,因此須要作隱藏操做。至於爲何都要添加到 maskContainer 上,是由於咱們不知道哪一個請求會最後結束,因此就採起每一個請求的遮罩咱們都添加,而後結束一個請求就移除一個遮罩,請求都結束的時候,遮罩也就都移除了。
  • maskContainerUIScrollView 的狀況作特殊處理,使其不可滾動。

而後是 hide 方法,內容以下:

func hide() {
    if let latestMask = self.maskContainer()?.latestMask() {
        latestMask.removeFromSuperview()

        if let container = self.maskContainer(), let scrollView = container as? UIScrollView {
            if false == latestMask.isHidden {
                scrollView.isScrollEnabled = true
            }
        }
    }
}
複製代碼

相比 show 方法,hide 方法作的事情要簡單一些,經過 MBContainable 協議上的 latestMask 方法獲取最新添加的、且遵循 MBMaskable 協議的 UIView,而後從 superview 上移除。對 maskContainerUIScrollView 的狀況作特殊處理,當被移除的遮罩是最後一個時,使其能夠再滾動。

MBLoadType

爲了下降使用成本,MBNetwork 提供了 MBLoadType 枚舉類型。

public enum MBLoadType {
    case none
    case `default`(container: MBContainable)
}
複製代碼

none:表示不須要加載。 default:傳入遵循 MBContainable 協議的 container 附加值。

而後對 MBLoadTypeextension,使其遵循 MBLoadable 協議。

extension MBLoadType: MBLoadable {
    public func maskContainer() -> MBContainable? {
        switch self {
        case .default(let container):
            return container
        case .none:
            return nil
        }
    }
}
複製代碼

這樣對於不須要加載或者只須要指定 maskContainer 的狀況(PS:好比全屏遮罩),就能夠直接用 MBLoadType 來代替 MBLoadable

經常使用控件支持

UIControl

  • maskContainer 就是自己,好比 UIButton,加載時直接在按鈕上顯示「菊花」便可。
  • mask 須要定製下,不能是默認的 MBMaskView,而應該是 MBActivityIndicator,而後 MBActivityIndicator 「菊花」的顏色和背景色應該和 UIControl 一致。
  • 加載開始和加載所有結束時須要設置 isEnabled

UIRefreshControl

  • 不須要顯示加載遮罩。
  • 加載開始和加載所有結束時須要調用 beginRefreshingendRefreshing

UITableViewCell

  • maskContainer 就是自己。
  • mask 須要定製下,不能是默認的 MBMaskView,而應該是 MBActivityIndicator,而後 MBActivityIndicator 「菊花」的顏色和背景色應該和 UIControl 一致。

結合網絡請求

至此,加載相關協議的定義和默認實現都已經完成。如今須要作的就是把加載和網絡請求結合起來,其實很簡單,以前 MBRequestable 協議擴展的網絡請求方法都返回了類型爲 DataRequestUploadRequest 或者 DownloadRequest 的對象,因此咱們對它們作 extension,而後實現下面的 load 方法便可。

func load(load: MBLoadable = MBLoadType.none) -> Self {
    load.begin()
    return response { (response: DefaultDataResponse) in
        load.end()
    }
}
複製代碼

傳入參數爲遵循 MBLoadable 協議的 load 對象,默認值爲 MBLoadType.none。請求開始時調用其 begin 方法,請求返回時調用其 end 方法。

使用方法

基礎用法

UIViewController 上顯示加載遮罩

request(WeatherForm()).load(load: MBLoadType.default(container: self))
複製代碼
UIButton 上顯示加載遮罩

request(WeatherForm()).load(load: button)
複製代碼
UITableViewCell 上顯示加載遮罩

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableView .deselectRow(at: indexPath, animated: false)
    let cell = tableView.cellForRow(at: indexPath)
    request(WeatherForm()).load(load: cell!)
}
複製代碼
UIRefreshControl

refresh.attributedTitle = NSAttributedString(string: "Loadable UIRefreshControl")
refresh.addTarget(self, action: #selector(LoadableTableViewController.refresh(refresh:)), for: .valueChanged)
tableView.addSubview(refresh)
     
func refresh(refresh: UIRefreshControl) {
    request(WeatherForm()).load(load: refresh)
}
複製代碼

進階

除了基本的用法,MBNetwork 還支持對加載進行徹底的自定義,作法以下:

首先,咱們建立一個遵循 MBLoadable 協議的類型 LoadConfig

class LoadConfig: MBLoadable {
    init(container: MBContainable? = nil, mask: MBMaskable? = MBMaskView(), inset: UIEdgeInsets = UIEdgeInsets.zero) {
        insetMine = inset
        maskMine = mask
        containerMine = container
    }
    
    func mask() -> MBMaskable? {
        return maskMine
    }
    
    func inset() -> UIEdgeInsets {
        return insetMine
    }
    
    func maskContainer() -> MBContainable? {
        return containerMine
    }
    
    func begin() {
        show()
    }
    
    func end() {
        hide()
    }
    
    var insetMine: UIEdgeInsets
    var maskMine: MBMaskable?
    var containerMine: MBContainable?
}
複製代碼

而後咱們就能夠這樣使用它了。

let load = LoadConfig(container: view, mask:MBEyeLoading(), inset: UIEdgeInsetsMake(30+64, 15, UIScreen.main.bounds.height-64-(44*4+30+15*3), 15))
request(WeatherForm()).load(load: load)
複製代碼

你會發現全部的東西都是能夠自定義的,並且使用起來仍然很簡單。

下面是利用 LoadConfigUITableView 上顯示自定義加載遮罩的的例子。

let load = LoadConfig(container:self.tableView, mask: MBActivityIndicator(), inset: UIEdgeInsetsMake(UIScreen.main.bounds.width - self.tableView.contentOffset.y > 0 ? UIScreen.main.bounds.width - self.tableView.contentOffset.y : 0, 0, 0, 0))
request(WeatherForm()).load(load: load)
        
複製代碼

加載進度展現

進度的展現比較簡單,只須要有方法實時更新進度便可,因此咱們先定義 MBProgressable 協議,內容以下:

public protocol MBProgressable {
    func progress(_ progress: Progress)
}
複製代碼

由於通常只有上傳和下載大文件才須要進度展現,因此咱們只對 UploadRequestDownloadRequestextension,添加 progress 方法,參數爲遵循 MBProgressable 協議的 progress 對象 :

func progress(progress: MBProgressable) -> Self {
    return uploadProgress { (prog: Progress) in
        progress.progress(prog)
    }
}

複製代碼

經常使用控件支持

既然是進度展現,固然得讓 UIProgressView 遵循 MBProgressable 協議,實現以下:

// MARK: - Making `UIProgressView` conforms to `MBLoadProgressable`
extension UIProgressView: MBProgressable {

    /// Updating progress
    ///
    /// - Parameter progress: Progress object generated by network request
    public func progress(_ progress: Progress) {
        self.setProgress(Float(progress.completedUnitCount).divided(by: Float(progress.totalUnitCount)), animated: true)
    }
}

複製代碼

而後咱們就能夠直接把 UIProgressView 對象當作 progress 方法的參數了。

download(ImageDownloadForm()).progress(progress: progress)
複製代碼

信息提示

信息提示包括兩個部分,出錯提示和成功提示。因此咱們先抽象了一個 MBMessageable 協議,協議的內容僅僅包含了顯示消息的容器。

public protocol MBMessageable {
    func messageContainer() -> MBContainable?
}
複製代碼

毫無疑問,返回的容器固然也是遵循 MBContainable 協議的,這個容器將被用來展現出錯和成功提示。

出錯提示

出錯提示須要作的事情有兩步:

  1. 解析錯誤信息
  2. 展現錯誤信息

首先咱們來完成第一步,解析錯誤信息。這裏咱們把錯誤信息抽象成協議 MBErrorable,其內容以下:

public protocol MBErrorable {

    /// Using this set with code to distinguish successful code from error code
    var successCodes: [String] { get }

    /// Using this code with successCodes set to distinguish successful code from error code
    var code: String? { get }

    /// Corresponding message
    var message: String? { get }
}
複製代碼

其中 successCodes 用來定義哪些錯誤碼是正常的; code 表示當前錯誤碼;message 定義了展現給用戶的信息。

具體怎麼使用這個協議後面再說,咱們接着看 JSON 錯誤解析協議 MBJSONErrorable

public protocol MBJSONErrorable: MBErrorable, Mappable {

}
複製代碼

注意這裏的 Mappable 協議來自 ObjectMapper,目的是讓遵循這個協議的對象實現 Mappable 協議中的 func mapping(map: Map) 方法,這個方法定義了 JSON 數據中錯誤信息到 MBErrorable 協議中 codemessage 屬性的映射關係。

假設服務端返回的 JSON 內容以下:

{
    "data": {
        "code": "200",    
        "message": "請求成功"
    }
}
複製代碼

那咱們的錯誤信息對象就能夠定義成下面的樣子。

class WeatherError: MBJSONErrorable {
    var successCodes: [String] = ["200"]

    var code: String?
    var message: String?

    init() { }

    required init?(map: Map) { }

    func mapping(map: Map) {
        code <- map["data.code"]
        message <- map["data.message"]
    }
}
複製代碼

ObjectMapper 會把 data.codedata.message 的值映射到 codemessage 屬性上。至此,錯誤信息的解析就完成了。

而後是第二步,錯誤信息展現。定義 MBWarnable 協議:

public protocol MBWarnable: MBMessageable {
    func show(error: MBErrorable?)
}
複製代碼

這個協議遵循 MBMessageable 協議。遵循這個協議的對象除了要實現 MBMessageable 協議的 messageContainer 方法,還須要實現 show 方法,這個方法只有一個參數,經過這個參數咱們傳入遵循錯誤信息協議的對象。

如今咱們就可使用 MBErrorableMBWarnable 協議來進行出錯提示了。和以前同樣咱們仍是對 DataRequest 作 extension。添加 warn 方法。

func warn<T: MBJSONErrorable>( error: T, warn: MBWarnable, completionHandler: ((MBJSONErrorable) -> Void)? = nil
        ) -> Self {

    return response(completionHandler: { (response: DefaultDataResponse) in
        if let err = response.error {
            warn.show(error: err.localizedDescription)
        }
    }).responseObject(queue: nil, keyPath: nil, mapToObject: nil, context: nil) { (response: DataResponse<T>) in
        if let err = response.result.value {
            if let code = err.code {
                if true == error.successCodes.contains(code) {
                    completionHandler?(err)
                } else {
                    warn.show(error: err)
                }
            }
        }
    }
}
複製代碼

這個方法包括三個參數:

  • error:遵循 MBJSONErrorable 協議的泛型錯誤解析對象。傳入這個對象到 AlamofireObjectMapperresponseObject 方法中便可得到服務端返回的錯誤信息。
  • warn:遵循 MBWarnable 協議的錯誤展現對象。
  • completionHandler:返回結果正確時調用的閉包。業務層通常經過這個閉包來作特殊錯誤碼處理。

作了以下的事情:

  • 經過 Alamofireresponse 方法獲取非業務錯誤信息,若是存在,則調用 warnshow 方法展現錯誤信息,這裏你們可能會有點疑惑:爲何能夠把 String 當作 MBErrorable 傳入到 show 方法中?這是由於咱們作了下面的事情:

    extension String: MBErrorable {
          public var message: String? {
              return self
          }
    }
    複製代碼
  • 經過 AlamofireObjectMapperresponseObject 方法獲取到服務端返回的錯誤信息,判斷返回的錯誤碼是否包含在 successCodes 中,若是是,則交給業務層處理;(PS:對於某些須要特殊處理的錯誤碼,也能夠定義在 successCodes 中,而後在業務層單獨處理。)不然,直接調用 warnshow 方法展現錯誤信息。

成功提示

相比錯誤提示,成功提示會簡單一些,由於成功提示信息通常都是在本地定義的,不須要從服務端獲取,因此成功提示協議的內容以下:

public protocol MBInformable: MBMessageable {
    func show()

    func message() -> String
}
複製代碼

包含兩個方法, show 方法用於展現信息;message 方法定義展現的信息。

而後對 DataRequest 作擴展,添加 inform 方法:

func inform<T: MBJSONErrorable>(error: T, inform: MBInformable) -> Self {

    return responseObject(queue: nil, keyPath: nil, mapToObject: nil, context: nil) { (response: DataResponse<T>) in
        if let err = response.result.value {
            if let code = err.code {
                if true == error.successCodes.contains(code) {
                    inform.show()
                }
            }
        }
    }
}
複製代碼

這裏一樣也傳入遵循 MBJSONErrorable 協議的泛型錯誤解析對象,由於若是服務端的返回結果是錯的,則不該該提示成功。仍是經過 AlamofireObjectMapperresponseObject 方法獲取到服務端返回的錯誤信息,判斷返回的錯誤碼是否包含在 successCodes 中,若是是,則經過 inform 對象 的 show 方法展現成功信息。

經常使用控件支持

觀察目前主流 App,信息提示通常是經過 UIAlertController 來展現的,因此咱們經過 extension 的方式讓 UIAlertController 遵循 MBWarnableMBInformable 協議。

extension UIAlertController: MBInformable {
    public func show() {
        UIApplication.shared.keyWindow?.rootViewController?.present(self, animated: true, completion: nil)
    }
}

extension UIAlertController: MBWarnable{
    public func show(error: MBErrorable?) {
        if let err = error {
            if "" != err.message {
                message = err.message
                
                UIApplication.shared.keyWindow?.rootViewController?.present(self, animated: true, completion: nil)
            }
        }
    }
}
複製代碼

發現這裏咱們沒有用到 messageContainer,這是由於對於 UIAlertController 來講,它的容器是固定的,使用 UIApplication.shared.keyWindow?.rootViewController? 便可。注意對於MBInformable,直接展現 UIAlertController, 而對於 MBWarnable,則是展現 error 中的 message

下面是使用的兩個例子:

這裏寫圖片描述

這裏寫圖片描述

let alert = UIAlertController(title: "Warning", message: "Network unavailable", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.cancel, handler: nil))
        
request(WeatherForm()).warn(
    error: WeatherError(),
    warn: alert
)

let alert = UIAlertController(title: "Notice", message: "Load successfully", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.cancel, handler: nil))
request(WeatherForm()).inform(
    error: WeatherInformError(),
    inform: alert
)
複製代碼

這樣就達到了業務層定義展現信息,MBNetwork 自動展現的效果,是否是簡單不少?至於擴展性,咱們仍是能夠參照 UIAlertController 的實現添加對其它第三方提示庫的支持。

從新請求

開發中……敬請期待

若有任何知識產權、版權問題或理論錯誤,還請指正。

轉載請註明原做者及以上信息。

相關文章
相關標籤/搜索