最近開源了一個面向協定設計的網絡請求庫 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
enum
做為檢驗寫 Swift 姿勢正确與否的重要名額,Alamofire 當然不會缺。
鍊式調用
這是讓 Alamofire 成為一個優雅的網絡架構的重要原因之一。這一點 MBNetwork 也進行了完全的 Copy。
@discardableResult
@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。這種做法有幾個優點:
- 對于類似
這樣的參數,一般全局都是一緻的,可以直接 extension 指定。headers
- 通過協定的名字即可知道表單的功能,簡單明确。
下面是 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
MBContainable
遵循這個協定的對象隻需要實作下面的方法即可:
func containerView() -> UIView?
這個方法傳回做為遮罩容器的
UIView
。做為遮罩的
UIView
最終會被添加到
containerView
上。
不同類型的容器的
containerView
是不一樣的,下面是各種類型容器
containerView
的清單:
容器 | |
---|---|
| |
| |
| |
| 最近一個不是 的 |
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
MBMaskable
協定内部隻定義了一個屬性
maskId
,作用是用來區分多種遮罩。
MBNetwork 内部實作了兩個遵循
MBMaskable
協定的
UIView
,分别是
MBActivityIndicator
和
MBMaskView
,其中
MBMaskView
的效果是參照
MBProgressHUD
實作,是以對于大部分場景來說,直接使用這兩個
UIView
即可。
注:協定唯一的作用是與
MBMaskable
上其它
containerView
做區分。
subview
MBLoadable
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
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
UIControl
-
就是本身,比如maskContainer
,加載時直接在按鈕上顯示“菊花”即可。UIButton
-
需要定制下,不能是預設的mask
,而應該是MBMaskView
,然後MBActivityIndicator
“菊花”的顔色和背景色應該和MBActivityIndicator
一緻。UIControl
- 加載開始和加載全部結束時需要設定
。isEnabled
UIRefreshControl
UIRefreshControl
- 不需要顯示加載遮罩。
- 加載開始和加載全部結束時需要調用
和beginRefreshing
。endRefreshing
UITableViewCell
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
上顯示加載遮罩
UIViewController
在 UIButton
上顯示加載遮罩
UIButton
在 UITableViewCell
上顯示加載遮罩
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
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(+, , UIScreen.main.bounds.height--(*++*), ))
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 > ? UIScreen.main.bounds.width - self.tableView.contentOffset.y : , , , ))
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
方法的參數了。
資訊提示
資訊提示包括兩個部分,出錯提示和成功提示。是以我們先抽象了一個
MBMessageable
協定,協定的内容僅僅包含了顯示消息的容器。
public protocol MBMessageable {
func messageContainer() -> MBContainable?
}
毫無疑問,傳回的容器當然也是遵循
MBContainable
協定的,這個容器将被用來展示出錯和成功提示。
出錯提示
出錯提示需要做的事情有兩步:
- 解析錯誤資訊
- 展示錯誤資訊
首先我們來完成第一步,解析錯誤資訊。這裡我們把錯誤資訊抽象成協定
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<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
協定的泛型錯誤解析對象。傳入這個對象到 AlamofireObjectMapper 的MBJSONErrorable
方法中即可獲得服務端傳回的錯誤資訊。responseObject
-
:遵循warn
協定的錯誤展示對象。MBWarnable
-
:傳回結果正确時調用的閉包。業務層一般通過這個閉包來做特殊錯誤碼處理。completionHandler
做了如下的事情:
- 通過 Alamofire 的
方法擷取非業務錯誤資訊,如果存在,則調用response
的warn
方法展示錯誤資訊,這裡大家可能會有點疑惑:為什麼可以把show
當做String
傳入到MBErrorable
方法中?這是因為我們做了下面的事情:show
extension String: MBErrorable { public var message: String? { return self } }
- 通過 AlamofireObjectMapper 的
方法擷取到服務端傳回的錯誤資訊,判斷傳回的錯誤碼是否包含在responseObject
中,如果是,則交給業務層處理;(PS:對于某些需要特殊處理的錯誤碼,也可以定義在successCodes
中,然後在業務層單獨處理。)否則,直接調用successCodes
的warn
方法展示錯誤資訊。show
成功提示
相比錯誤提示,成功提示會簡單一些,因為成功提示資訊一般都是在本地定義的,不需要從服務端擷取,是以成功提示協定的内容如下:
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
協定的泛型錯誤解析對象,因為如果服務端的傳回結果是錯的,則不應該提示成功。還是通過 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
的實作添加對其它第三方提示庫的支援。
重新請求
開發中……敬請期待