你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> 如何設計一個面向協議的 iOS 網絡請求庫

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

編輯:IOS開發綜合

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

需要干些啥

對於大部分 App 而言,業務層做一次網絡請求通常關心的問題有如下幾個:

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

為什麼是POP而不是OOP

關於POP和OOP這兩種設計思想及其特點的文章很多,所以我就不廢話了,主要說說為啥要用POP來寫MBNetwork。

  • 想嘗試一下一切皆協議的設計方式。所以這個庫的設計只是一次極限嘗試,並不代表這就是最完美的設計方式。
  • 如果以OOP的方式實現,使用者需要通過繼承的方式來獲得某個類實現的功能,如果使用者還需要另外某個類實現的功能,就會很尴尬。而POP是通過對協議進行擴展來實現功能,使用者可以同時遵循多個協議,輕松解決OOP的這個硬傷。
  • OOP繼承的方式會使某些子類獲得它們不需要的功能。
  • 如果因為業務的增多,需要對某些業務進行分離,OOP的方式還是會碰到子類不能繼承多個父類的問題,而POP則完全不會,分離之後,只需要遵循分離後的多個協議即可。
  • OOP繼承的方式入侵性比較強。
  • POP可以通過擴展的方式對各個協議進行默認實現,降低使用者的學習成本。
  • 同時POP還能讓使用者對協議做自定義的實現,保證其高度可配置性。

站在Alamofire的肩膀上

很多人都喜歡說Alamofire是Swift版本的AFNetworking,但是在我看來,Alamofire比AFNetworking更純粹。這和Swift語言本身的特性也是有關系的,Swift開發者們,更喜歡寫一些輕量的框架。比如AFNetworking把很多 UI 相關的擴展功能都做在框架內,而Alamofire的做法則是放在另外的擴展庫中。比如AlamofireImage和AlamofireNetworkActivityIndicator

而MBNetwork就可以當做是Alamofire的一個擴展庫,所以,MBNetwork很大程度上遵循了Alamofire接口的設計規范。一方面,降低了MBNetwork的學習成本,另一方面,從個人角度來看,Alamofire確實有很多特別值得借鑒的地方。

POP

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

enum

做為檢驗寫Swift姿勢正確與否的重要指標,Alamofire當然不會缺。

鏈式調用

這是讓Alamofire成為一個優雅的網絡框架的重要原因之一。這一點MBNetwork也進行了完全的 Copy。

@discardableResult

在Alamofire所有帶返回值的方法前面,都會有這麼一個標簽,其實作用很簡單,因為在Swift中,返回值如果沒有被使用,Xcode 會產生告警信息。加上這個標簽之後,表示這個方法的返回值就算沒有被使用,也不產生告警。

當然還有ObjectMapper

引入ObjectMapper很大一部分原因是需要做錯誤和成功提示。因為只有解析服務端的錯誤信息節點才能知道返回結果是否正確,所以我們引入ObjectMapper來做JSON解析。 而只做JSON解析的原因是目前主流的服務端客戶端數據交互格式是JSON。

這裡需要提到的就是另外一個Alamofire的擴展庫AlamofireObjectMapper,從名字就可以看出來,這個庫就是參照Alamofire的 API 規范來做ObjectMapper做的事情。這個庫的代碼很少,但實現方式非常Alamofire,大家可以拜讀一下它的源碼,基本上就知道如何基於Alamofire做自定義數據解析了。

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

一步一步來

表單創建

Alamofire的請求有三種:request、upload和download,這三種請求都有相應的參數,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對應的接口。注意它們都返回了類型為DataRequest、UploadRequest或者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 最近一個不是UIScrollView的superview

UIScrollView這個地方有點特殊,因為如果直接在UIScrollView上添加遮罩視圖,遮罩視圖的中心點是非常難控制的,所以這裡用了一個技巧,遞歸尋找UIScrollView的superview,發現不是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
}

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

MBMaskable

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

MBNetwork內部實現了兩個遵循MBMaskable協議的UIView,分別是MBActivityIndicator和MBMaskView,其中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。如果有,就把新添加的這個遮罩視圖隱藏起來,再添加到maskContainer的containerView上。為什麼會有多個遮罩的原因是多個網絡請求可能同時遮罩某一個maskContainer,另外,多個遮罩不能都顯示出來,因為有的遮罩可能有半透明部分,所以需要做隱藏操作。至於為什麼都要添加到maskContainer上,是因為我們不知道哪個請求會最後結束,所以就采取每個請求的遮罩我們都添加,然後結束一個請求就移除一個遮罩,請求都結束的時候,遮罩也就都移除了。
  • 對maskContainer是UIScrollView的情況做特殊處理,使其不可滾動。

然後是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上移除。對maskContainer是UIScrollView的情況做特殊處理,當被移除的遮罩是最後一個時,使其可以再滾動。

MBLoadType

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

public enum MBLoadType {
    case none
    case `default`(container: MBContainable)
}

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

然後對MBLoadType做extension,使其遵循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

  • 不需要顯示加載遮罩。
  • 加載開始和加載全部結束時需要調用beginRefreshing和endRefreshing。

UITableViewCell

  • maskContainer就是本身。
  • mask需要定制下,不能是默認的MBMaskView,而應該是MBActivityIndicator,然後MBActivityIndicator“菊花”的顏色和背景色應該和UIControl一致。

結合網絡請求

至此,加載相關協議的定義和默認實現都已經完成。現在需要做的就是把加載和網絡請求結合起來,其實很簡單,之前MBRequestable協議擴展的網絡請求方法都返回了類型為DataRequest、UploadRequest或者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上顯示加載遮罩

\<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:java;"> 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)

你會發現所有的東西都是可以自定義的,而且使用起來仍然很簡單。

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

\


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)
}

因為一般只有上傳和下載大文件才需要進度展示,所以我們只對UploadRequest和DownloadRequest做extension,添加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協議中code和message屬性的映射關系。

假設服務端返回的 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.code和data.message的值映射到code和message屬性上。至此,錯誤信息的解析就完成了。

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

public protocol MBWarnable: MBMessageable {
    func show(error: MBErrorable?)
}

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

現在我們就可以使用MBErrorable和MBWarnable協議來進行出錯提示了。和之前一樣我們還是對DataRequest做 extension。添加warn方法。

func warn(
        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) 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協議的泛型錯誤解析對象。傳入這個對象到AlamofireObjectMapper的responseObject方法中即可獲得服務端返回的錯誤信息。
  • warn:遵循MBWarnable協議的錯誤展示對象。
  • completionHandler:返回結果正確時調用的閉包。業務層一般通過這個閉包來做特殊錯誤碼處理。

做了如下的事情:

  • 通過Alamofire的response方法獲取非業務錯誤信息,如果存在,則調用warn的show方法展示錯誤信息,這裡大家可能會有點疑惑:為什麼可以把String當做MBErrorable傳入到show方法中?這是因為我們做了下面的事情:
extension String: MBErrorable {
    public var message: String? {
        return self
    }
}
通過AlamofireObjectMapper的responseObject方法獲取到服務端返回的錯誤信息,判斷返回的錯誤碼是否包含在successCodes中,如果是,則交給業務層處理;(PS:對於某些需要特殊處理的錯誤碼,也可以定義在successCodes中,然後在業務層單獨處理。)否則,直接調用warn的show方法展示錯誤信息。

成功提示

相比錯誤提示,成功提示會簡單一些,因為成功提示信息一般都是在本地定義的,不需要從服務端獲取,所以成功提示協議的內容如下:

public protocol MBInformable: MBMessageable {
    func show()

    func message() -> String
}

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

然後對DataRequest做擴展,添加inform方法:

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

    return responseObject(queue: nil, keyPath: nil, mapToObject: nil, context: nil) { (response: DataResponse) in
        if let err = response.result.value {
            if let code = err.code {
                if true == error.successCodes.contains(code) {
                    inform.show()
                }
            }
        }
    }
}
這裡同樣也傳入遵循MBJSONErrorable協議的泛型錯誤解析對象,因為如果服務端的返回結果是錯的,則不應該提示成功。還是通過AlamofireObjectMapper的responseObject方法獲取到服務端返回的錯誤信息,判斷返回的錯誤碼是否包含在successCodes中,如果是,則通過inform對象 的show方法展示成功信息。

常用控件支持

觀察目前主流 App,信息提示一般是通過UIAlertController來展示的,所以我們通過 extension 的方式讓UIAlertController遵循MBWarnable和MBInformable協議。

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的實現添加對其它第三方提示庫的支持。

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved