国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統(tǒng) > iOS > 正文

造輪子 | 如何設(shè)計(jì)一個(gè)面向協(xié)議的 iOS 網(wǎng)絡(luò)請求庫

2019-11-09 18:15:15
字體:
供稿:網(wǎng)友

最近開源了一個(gè)面向協(xié)議設(shè)計(jì)的網(wǎng)絡(luò)請求庫 MBNetwork,基于 AlamofireObjectMapper 實(shí)現(xiàn),目的是簡化業(yè)務(wù)層的網(wǎng)絡(luò)請求操作。

需要干些啥

對于大部分 App 而言,業(yè)務(wù)層做一次網(wǎng)絡(luò)請求通常關(guān)心的問題有如下幾個(gè):

如何在任意位置發(fā)起網(wǎng)絡(luò)請求。表單創(chuàng)建。包含請求地址、請求方式(GET/POST/……)、請求頭等……加載遮罩。目的是阻塞 UI 交互,同時(shí)告知用戶操作正在進(jìn)行。比如提交表單時(shí)在提交按鈕上顯示 “菊花”,同時(shí)使其失效。加載進(jìn)度展示。下載上傳圖片等資源時(shí)提示用戶當(dāng)前進(jìn)度。斷點(diǎn)續(xù)傳。下載上傳圖片等資源發(fā)生錯(cuò)誤時(shí)可以在之前已完成部分的基礎(chǔ)上繼續(xù)操作,這個(gè) Alamofire 可以支持。數(shù)據(jù)解析。因?yàn)槟壳爸髁鞣?wù)端和客戶端數(shù)據(jù)交換采用的格式是 JSON,所以我們暫時(shí)先考慮 JSON 格式的數(shù)據(jù)解析,這個(gè) ObjectMapper 可以支持。出錯(cuò)提示。發(fā)生業(yè)務(wù)異常時(shí),直接顯示服務(wù)端返回的異常信息。前提是服務(wù)端異常信息足夠友好。成功提示。請求正常結(jié)束時(shí)提示用戶。網(wǎng)絡(luò)異常重新請求。顯示網(wǎng)絡(luò)異常界面,點(diǎn)擊之后重新發(fā)送請求。

為什么是 POP 而不是 OOP

關(guān)于 POPOOP 這兩種設(shè)計(jì)思想及其特點(diǎn)的文章很多,所以我就不廢話了,主要說說為啥要用 POP 來寫 MBNetwork

想嘗試一下一切皆協(xié)議的設(shè)計(jì)方式。所以這個(gè)庫的設(shè)計(jì)只是一次極限嘗試,并不代表這就是最完美的設(shè)計(jì)方式。如果以 OOP 的方式實(shí)現(xiàn),使用者需要通過繼承的方式來獲得某個(gè)類實(shí)現(xiàn)的功能,如果使用者還需要另外某個(gè)類實(shí)現(xiàn)的功能,就會很尷尬。而 POP 是通過對協(xié)議進(jìn)行擴(kuò)展來實(shí)現(xiàn)功能,使用者可以同時(shí)遵循多個(gè)協(xié)議,輕松解決 OOP 的這個(gè)硬傷。OOP 繼承的方式會使某些子類獲得它們不需要的功能。如果因?yàn)闃I(yè)務(wù)的增多,需要對某些業(yè)務(wù)進(jìn)行分離,OOP 的方式還是會碰到子類不能繼承多個(gè)父類的問題,而 POP 則完全不會,分離之后,只需要遵循分離后的多個(gè)協(xié)議即可。OOP 繼承的方式入侵性比較強(qiáng)。POP 可以通過擴(kuò)展的方式對各個(gè)協(xié)議進(jìn)行默認(rèn)實(shí)現(xiàn),降低使用者的學(xué)習(xí)成本。同時(shí) POP 還能讓使用者對協(xié)議做自定義的實(shí)現(xiàn),保證其高度可配置性。

站在 Alamofire 的肩膀上

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

MBNetwork 就可以當(dāng)做是 Alamofire 的一個(gè)擴(kuò)展庫,所以,MBNetwork 很大程度上遵循了 Alamofire 接口的設(shè)計(jì)規(guī)范。一方面,降低了 MBNetwork 的學(xué)習(xí)成本,另一方面,從個(gè)人角度來看,Alamofire 確實(shí)有很多特別值得借鑒的地方。

POP

首先當(dāng)然是 POP 啦,Alamofire 大量運(yùn)用了 PRotocol + extension 的實(shí)現(xiàn)方式。

enum

做為檢驗(yàn)寫 Swift 姿勢正確與否的重要指標(biāo),Alamofire 當(dāng)然不會缺。

鏈?zhǔn)秸{(diào)用

這是讓 Alamofire 成為一個(gè)優(yōu)雅的網(wǎng)絡(luò)框架的重要原因之一。這一點(diǎn) MBNetwork 也進(jìn)行了完全的 Copy。

@discardableResult

Alamofire 所有帶返回值的方法前面,都會有這么一個(gè)標(biāo)簽,其實(shí)作用很簡單,因?yàn)樵?Swift 中,返回值如果沒有被使用,Xcode 會產(chǎn)生告警信息。加上這個(gè)標(biāo)簽之后,表示這個(gè)方法的返回值就算沒有被使用,也不產(chǎn)生告警。

當(dāng)然還有 ObjectMapper

引入 ObjectMapper 很大一部分原因是需要做錯(cuò)誤和成功提示。因?yàn)橹挥薪馕龇?wù)端的錯(cuò)誤信息節(jié)點(diǎn)才能知道返回結(jié)果是否正確,所以我們引入 ObjectMapper 來做 JSON 解析。 而只做 JSON 解析的原因是目前主流的服務(wù)端客戶端數(shù)據(jù)交互格式是 JSON

這里需要提到的就是另外一個(gè) Alamofire 的擴(kuò)展庫 AlamofireObjectMapper,從名字就可以看出來,這個(gè)庫就是參照 Alamofire 的 API 規(guī)范來做 ObjectMapper 做的事情。這個(gè)庫的代碼很少,但實(shí)現(xiàn)方式非常 Alamofire,大家可以拜讀一下它的源碼,基本上就知道如何基于 Alamofire 做自定義數(shù)據(jù)解析了。

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

一步一步來

表單創(chuàng)建

Alamofire 的請求有三種: requestuploaddownload,這三種請求都有相應(yīng)的參數(shù),MBNetwork 把這些參數(shù)抽象成了對應(yīng)的協(xié)議,具體內(nèi)容參見:MBForm.swift。這種做法有幾個(gè)優(yōu)點(diǎn):

對于類似 headers 這樣的參數(shù),一般全局都是一致的,可以直接 extension 指定。通過協(xié)議的名字即可知道表單的功能,簡單明確。

下面是 MBNetwork 表單協(xié)議的用法舉例:

指定全局 headers 參數(shù):

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

創(chuàng)建具體業(yè)務(wù)表單:

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}

表單協(xié)議化可能有過度設(shè)計(jì)的嫌疑,有同感的仍然可以使用 Alamofire 對應(yīng)的接口去做網(wǎng)絡(luò)請求,不影響 MBNetwork 其他功能的使用。

基于表單請求數(shù)據(jù)

表單已經(jīng)抽象成協(xié)議,現(xiàn)在就可以基于表單發(fā)送網(wǎng)絡(luò)請求了,因?yàn)橹耙呀?jīng)說過需要在任意位置發(fā)送網(wǎng)絡(luò)請求,而實(shí)現(xiàn)這一點(diǎn)的方法基本就這幾種:

單例。全局方法,Alamofire 就是這么干的。協(xié)議擴(kuò)展。

MBNetwork 采用了最后一種方法。原因很簡單,MBNetwork 是以一切皆協(xié)議的原則設(shè)計(jì)的,所以我們把網(wǎng)絡(luò)請求抽象成 MBRequestable 協(xié)議。

首先,MBRequestable 是一個(gè)空協(xié)議 。

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

為什么是空協(xié)議,因?yàn)椴恍枰裱@個(gè)協(xié)議的對象干啥。

然后對它做 extension,實(shí)現(xiàn)網(wǎng)絡(luò)請求相關(guān)的一系列接口:

func request(_ form: MBRequestFormable) -> DataRequestfunc download(_ form: MBDownloadFormable) -> DownloadRequestfunc download(_ form: MBDownloadResumeFormable) -> DownloadRequestfunc upload(_ form: MBUploadDataFormable) -> UploadRequestfunc upload(_ form: MBUploadFileFormable) -> UploadRequestfunc upload(_ form: MBUploadStreamFormable) -> UploadRequestfunc upload(_ form: MBUploadMultiFormDataFormable, completion: ((UploadRequest) -> Void)?)

這些就是網(wǎng)絡(luò)請求的接口,參數(shù)是各種表單協(xié)議,接口內(nèi)部調(diào)用的其實(shí)是 Alamofire 對應(yīng)的接口。注意它們都返回了類型為 DataRequestUploadRequest 或者 DownloadRequest 的對象,通過返回值我們可以繼續(xù)調(diào)用其他方法。

到這里 MBRequestable 的實(shí)現(xiàn)就完成了,使用方法很簡單,只需要設(shè)置類型遵循 MBRequestable 協(xié)議,就可以在該類型內(nèi)發(fā)起網(wǎng)絡(luò)請求。如下:

class LoadableViewController: UIViewController, MBRequestable { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. request(WeatherForm()) }}

加載

對于加載我們關(guān)心的點(diǎn)有如下幾個(gè):

加載開始需要干啥。加載結(jié)束需要干啥。是否需要顯示加載遮罩。在何處顯示遮罩。顯示遮罩的內(nèi)容。

對于這幾點(diǎn),我對協(xié)議的劃分是這樣的:

MBContainable 協(xié)議。遵循該協(xié)議的對象可以做為加載的容器。MBMaskable 協(xié)議。遵循該協(xié)議的 UIView 可以做為加載遮罩。MBLoadable 協(xié)議。遵循該協(xié)議的對象可以定義加載的配置和流程。

MBContainable

遵循這個(gè)協(xié)議的對象只需要實(shí)現(xiàn)下面的方法即可:

func containerView() -> UIView?

這個(gè)方法返回做為遮罩容器的 UIView。做為遮罩的 UIView 最終會被添加到 containerView 上。

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

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

UIScrollView 這個(gè)地方有點(diǎn)特殊,因?yàn)槿绻苯釉?UIScrollView 上添加遮罩視圖,遮罩視圖的中心點(diǎn)是非常難控制的,所以這里用了一個(gè)技巧,遞歸尋找 UIScrollViewsuperview,發(fā)現(xiàn)不是 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,添加一個(gè) latestMask 方法,這個(gè)方法實(shí)現(xiàn)的功能很簡單,就是返回 containerView 上最新添加的、而且遵循 MBMaskable 協(xié)議的 subview

MBMaskable

協(xié)議內(nèi)部只定義了一個(gè)屬性 maskId,作用是用來區(qū)分多種遮罩。

MBNetwork 內(nèi)部實(shí)現(xiàn)了兩個(gè)遵循 MBMaskable 協(xié)議的 UIView,分別是 MBActivityIndicatorMBMaskView,其中 MBMaskView 的效果是參照 MBProgressHUD 實(shí)現(xiàn),所以對于大部分場景來說,直接使用這兩個(gè) UIView 即可。

注:MBMaskable 協(xié)議唯一的作用是與 containerView 上其它 subview 做區(qū)分。

MBLoadable

做為加載協(xié)議的核心部分,MBLoadable 包含如下幾個(gè)部分:

func mask() -> MBMaskable?:遮罩視圖,可選的原因是可能不需要遮罩。func inset() -> UIEdgeInsets:遮罩視圖和容器視圖的邊距,默認(rèn)值 UIEdgeInsets.zerofunc maskContainer() -> MBContainable?:遮罩容器視圖,可選的原因是可能不需要遮罩。func begin():加載開始回調(diào)方法。func end():加載結(jié)束回調(diào)方法。

然后對協(xié)議要求實(shí)現(xiàn)的幾個(gè)方法做默認(rèn)實(shí)現(xiàn):

func mask() -> MBMaskable? { return MBMaskView() // 默認(rèn)顯示 MBProgressHUD 效果的遮罩。} func inset() -> UIEdgeInsets { return UIEdgeInsets.zero // 默認(rèn)邊距為 0 。}func maskContainer() -> MBContainable? { return nil // 默認(rèn)沒有遮罩容器。}func begin() { show() // 默認(rèn)調(diào)用 show 方法。}func end() { hide() // 默認(rèn)調(diào)用 hide 方法。}

上述代碼中的 show 方法和 hide 方法是實(shí)現(xiàn)加載遮罩的核心代碼。

show 方法的內(nèi)容如下:

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

這個(gè)方法做了下面幾件事情:

判斷 mask 方法返回的是不是遵循 MBMaskable 協(xié)議的 UIView,因?yàn)槿绻皇?UIView,不能被添加到其它的 UIView 上。通過 MBContainable 協(xié)議上的 latestMask 方法獲取最新添加的、且遵循 MBMaskable 協(xié)議的 UIView。如果有,就把新添加的這個(gè)遮罩視圖隱藏起來,再添加到 maskContainercontainerView 上。為什么會有多個(gè)遮罩的原因是多個(gè)網(wǎng)絡(luò)請求可能同時(shí)遮罩某一個(gè) maskContainer,另外,多個(gè)遮罩不能都顯示出來,因?yàn)橛械恼谡挚赡苡邪胪该鞑糠郑孕枰鲭[藏操作。至于為什么都要添加到 maskContainer 上,是因?yàn)槲覀儾恢滥膫€(gè)請求會最后結(jié)束,所以就采取每個(gè)請求的遮罩我們都添加,然后結(jié)束一個(gè)請求就移除一個(gè)遮罩,請求都結(jié)束的時(shí)候,遮罩也就都移除了。對 maskContainerUIScrollView 的情況做特殊處理,使其不可滾動(dòng)。

然后是 hide 方法,內(nèi)容如下:

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 協(xié)議上的 latestMask 方法獲取最新添加的、且遵循 MBMaskable 協(xié)議的 UIView,然后從 superview 上移除。對 maskContainerUIScrollView 的情況做特殊處理,當(dāng)被移除的遮罩是最后一個(gè)時(shí),使其可以再滾動(dòng)。

MBLoadType

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

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

none:表示不需要加載。 default:傳入遵循 MBContainable 協(xié)議的 container 附加值。

然后對 MBLoadTypeextension,使其遵循 MBLoadable 協(xié)議。

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,加載時(shí)直接在按鈕上顯示“菊花”即可。mask 需要定制下,不能是默認(rèn)的 MBMaskView,而應(yīng)該是 MBActivityIndicator,然后 MBActivityIndicator “菊花”的顏色和背景色應(yīng)該和 UIControl 一致。加載開始和加載全部結(jié)束時(shí)需要設(shè)置 isEnabled

UIRefreshControl

不需要顯示加載遮罩。加載開始和加載全部結(jié)束時(shí)需要調(diào)用 beginRefreshingendRefreshing

UITableViewCell

maskContainer 就是本身。mask 需要定制下,不能是默認(rèn)的 MBMaskView,而應(yīng)該是 MBActivityIndicator,然后 MBActivityIndicator “菊花”的顏色和背景色應(yīng)該和 UIControl 一致。

結(jié)合網(wǎng)絡(luò)請求

至此,加載相關(guān)協(xié)議的定義和默認(rèn)實(shí)現(xiàn)都已經(jīng)完成。現(xiàn)在需要做的就是把加載和網(wǎng)絡(luò)請求結(jié)合起來,其實(shí)很簡單,之前 MBRequestable 協(xié)議擴(kuò)展的網(wǎng)絡(luò)請求方法都返回了類型為 DataRequestUploadRequest 或者 DownloadRequest 的對象,所以我們對它們做 extension,然后實(shí)現(xiàn)下面的 load 方法即可。

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

傳入?yún)?shù)為遵循 MBLoadable 協(xié)議的 load 對象,默認(rèn)值為 MBLoadType.none。請求開始時(shí)調(diào)用其 begin 方法,請求返回時(shí)調(diào)用其 end 方法。

使用方法

基礎(chǔ)用法

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

進(jìn)階

除了基本的用法,MBNetwork 還支持對加載進(jìn)行完全的自定義,做法如下:

首先,我們創(chuàng)建一個(gè)遵循 MBLoadable 協(xié)議的類型 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)

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

下面是利用 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)

加載進(jìn)度展示

進(jìn)度的展示比較簡單,只需要有方法實(shí)時(shí)更新進(jìn)度即可,所以我們先定義 MBProgressable 協(xié)議,內(nèi)容如下:

public protocol MBProgressable { func progress(_ progress: Progress)}

因?yàn)橐话阒挥猩蟼骱拖螺d大文件才需要進(jìn)度展示,所以我們只對 UploadRequestDownloadRequestextension,添加 progress 方法,參數(shù)為遵循 MBProgressable 協(xié)議的 progress 對象 :

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

常用控件支持

既然是進(jìn)度展示,當(dāng)然得讓 UiprogressView 遵循 MBProgressable 協(xié)議,實(shí)現(xiàn)如下:

// 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 對象當(dāng)做 progress 方法的參數(shù)了。

download(ImageDownloadForm()).progress(progress: progress)

信息提示

信息提示包括兩個(gè)部分,出錯(cuò)提示和成功提示。所以我們先抽象了一個(gè) MBMessageable 協(xié)議,協(xié)議的內(nèi)容僅僅包含了顯示消息的容器。

public protocol MBMessageable { func messageContainer() -> MBContainable?}

毫無疑問,返回的容器當(dāng)然也是遵循 MBContainable 協(xié)議的,這個(gè)容器將被用來展示出錯(cuò)和成功提示。

出錯(cuò)提示

出錯(cuò)提示需要做的事情有兩步:

解析錯(cuò)誤信息展示錯(cuò)誤信息

首先我們來完成第一步,解析錯(cuò)誤信息。這里我們把錯(cuò)誤信息抽象成協(xié)議 MBErrorable,其內(nèi)容如下:

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 用來定義哪些錯(cuò)誤碼是正常的; code 表示當(dāng)前錯(cuò)誤碼;message 定義了展示給用戶的信息。

具體怎么使用這個(gè)協(xié)議后面再說,我們接著看 JSON 錯(cuò)誤解析協(xié)議 MBJSONErrorable

public protocol MBJSONErrorable: MBErrorable, Mappable {}

注意這里的 Mappable 協(xié)議來自 ObjectMapper,目的是讓遵循這個(gè)協(xié)議的對象實(shí)現(xiàn) Mappable 協(xié)議中的 func mapping(map: Map) 方法,這個(gè)方法定義了 JSON 數(shù)據(jù)中錯(cuò)誤信息到 MBErrorable 協(xié)議中 codemessage 屬性的映射關(guān)系。

假設(shè)服務(wù)端返回的 JSON 內(nèi)容如下:

{ "data": { "code": "200", "message": "請求成功" }}

那我們的錯(cuò)誤信息對象就可以定義成下面的樣子。

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 屬性上。至此,錯(cuò)誤信息的解析就完成了。

然后是第二步,錯(cuò)誤信息展示。定義 MBWarnable 協(xié)議:

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

這個(gè)協(xié)議遵循 MBMessageable 協(xié)議。遵循這個(gè)協(xié)議的對象除了要實(shí)現(xiàn) MBMessageable 協(xié)議的 messageContainer 方法,還需要實(shí)現(xiàn) show 方法,這個(gè)方法只有一個(gè)參數(shù),通過這個(gè)參數(shù)我們傳入遵循錯(cuò)誤信息協(xié)議的對象。

現(xiàn)在我們就可以使用 MBErrorableMBWarnable 協(xié)議來進(jìn)行出錯(cuò)提示了。和之前一樣我們還是對 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) } } } }}

這個(gè)方法包括三個(gè)參數(shù):

error:遵循 MBJSONErrorable 協(xié)議的泛型錯(cuò)誤解析對象。傳入這個(gè)對象到 AlamofireObjectMapperresponseObject 方法中即可獲得服務(wù)端返回的錯(cuò)誤信息。warn:遵循 MBWarnable 協(xié)議的錯(cuò)誤展示對象。 completionHandler:返回結(jié)果正確時(shí)調(diào)用的閉包。業(yè)務(wù)層一般通過這個(gè)閉包來做特殊錯(cuò)誤碼處理。

做了如下的事情:

通過 Alamofireresponse 方法獲取非業(yè)務(wù)錯(cuò)誤信息,如果存在,則調(diào)用 warnshow 方法展示錯(cuò)誤信息,這里大家可能會有點(diǎn)疑惑:為什么可以把 String 當(dāng)做 MBErrorable 傳入到 show 方法中?這是因?yàn)槲覀冏隽讼旅娴氖虑椋?/p>extension String: MBErrorable { public var message: String? { return self }}

通過 AlamofireObjectMapperresponseObject 方法獲取到服務(wù)端返回的錯(cuò)誤信息,判斷返回的錯(cuò)誤碼是否包含在 successCodes 中,如果是,則交給業(yè)務(wù)層處理;(PS:對于某些需要特殊處理的錯(cuò)誤碼,也可以定義在 successCodes 中,然后在業(yè)務(wù)層單獨(dú)處理。)否則,直接調(diào)用 warnshow 方法展示錯(cuò)誤信息。

成功提示

相比錯(cuò)誤提示,成功提示會簡單一些,因?yàn)槌晒μ崾拘畔⒁话愣际窃诒镜囟x的,不需要從服務(wù)端獲取,所以成功提示協(xié)議的內(nèi)容如下:

public protocol MBInformable: MBMessageable { func show() func message() -> String}

包含兩個(gè)方法, show 方法用于展示信息;message 方法定義展示的信息。

然后對 DataRequest 做擴(kuò)展,添加 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 協(xié)議的泛型錯(cuò)誤解析對象,因?yàn)槿绻?wù)端的返回結(jié)果是錯(cuò)的,則不應(yīng)該提示成功。還是通過 AlamofireObjectMapperresponseObject 方法獲取到服務(wù)端返回的錯(cuò)誤信息,判斷返回的錯(cuò)誤碼是否包含在 successCodes 中,如果是,則通過 inform 對象 的 show 方法展示成功信息。

常用控件支持

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

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

發(fā)現(xiàn)這里我們沒有用到 messageContainer,這是因?yàn)閷τ?UIAlertController 來說,它的容器是固定的,使用 UIApplication.shared.keyWindow?.rootViewController? 即可。注意對于MBInformable,直接展示 UIAlertController, 而對于 MBWarnable,則是展示 error 中的 message

下面是使用的兩個(gè)例子:

這里寫圖片描述

這里寫圖片描述

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)

這樣就達(dá)到了業(yè)務(wù)層定義展示信息,MBNetwork 自動(dòng)展示的效果,是不是簡單很多?至于擴(kuò)展性,我們還是可以參照 UIAlertController 的實(shí)現(xiàn)添加對其它第三方提示庫的支持。

重新請求

開發(fā)中……敬請期待


發(fā)表評論 共有條評論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 星子县| 九江市| 通化县| 嘉定区| 平果县| 弥渡县| 蓬莱市| 馆陶县| 阳新县| 关岭| 马鞍山市| 邳州市| 浪卡子县| 广西| 云龙县| 循化| 崇文区| 年辖:市辖区| 佛学| 屏山县| 清河县| 乌兰县| 正阳县| 唐河县| 乌拉特前旗| 巴楚县| 江孜县| 澜沧| 吴忠市| 通河县| 柯坪县| 南开区| 涟源市| 平阴县| 通渭县| 闸北区| 洛南县| 西城区| 马龙县| 桐梓县| 陆丰市|