造轮子 | 怎样设计一个面向协议的 iOS 网络请求库

近期开源了一个面向协议设计的网络请求库 MBNetwork,基于 Alamofire 和 ObjectMapper 实现,目的是简化业务层的网络请求操做。git

需要干些啥

对于大部分 App 而言,业务层作一次网络请求一般关心的问题有例如如下几个:github

  • 怎样在任何位置发起网络请求。

  • 表单建立。

    包括请求地址、请求方式(GET/POST/……)、请求头等……sql

  • 载入遮罩。

    目的是堵塞 UI 交互,同一时候告知用户操做正在进行。json

    比方提交表单时在提交按钮上显示 “菊花”,同一时候使其失效。swift

  • 载入进度展现。下载上传图片等资源时提示用户当前进度。
  • 断点续传。下载上传图片等资源错误发生时可以在以前已完毕部分的基础上继续操做。这个 Alamofire 可以支持。
  • 数据解析。因为眼下主流服务端和client数据交换採用的格式是 JSON,因此咱们临时先考虑 JSON 格式的数据解析,这个 ObjectMapper 可以支持。

  • 出错提示。发生业务异常时,直接显示服务端返回的异常信息。前提是服务端异常信息足够友好。
  • 成功提示。请求正常结束时提示用户。

  • 网络异常又一次请求。显示网络异常界面,点击以后又一次发送请求。

为何是 POP 而不是 OOP

关于 POP 和 OOP 这两种设计思想及其特色的文章很是多,因此我就不废话了。主要说说为啥要用 POP 来写 MBNetwork。ruby

  • 想尝试一下一切皆协议的设计方式。因此这个库的设计仅仅是一次极限尝试,并不表明这就是最完美的设计方式。
  • 假设以 OOP 的方式实现,使用者需要经过继承的方式来得到某个类实现的功能。假设使用者还需要另外某个类实现的功能,就会很是尴尬。

    而 POP 是经过对协议进行扩展来实现功能。使用者可以同一时候遵循多个协议。轻松解决 OOP 的这个硬伤。markdown

  • OOP 继承的方式会使某些子类得到它们不需要的功能。
  • 假设因为业务的增多,需要对某些业务进行分离,OOP 的方式仍是会碰到子类不能继承多个父类的问题,而 POP 则全然不会,分离以后。仅仅需要遵循分离后的多个协议就能够。
  • OOP 继承的方式入侵性比較强。

  • POP 可以经过扩展的方式对各个协议进行默认实现,减小使用者的学习成本。

  • 同一时候 POP 还能让使用者对协议作本身定义的实现,保证其高度可配置性。

站在 Alamofire 的肩膀上

很是多人都喜欢说 Alamofire 是 Swift 版本号的 AFNetworking。但是在我看来,Alamofire 比 AFNetworking 更纯粹。这和 Swift 语言自己的特性也是有关系的。Swift 开发人员们,更喜欢写一些轻量的框架。比方 AFNetworking 把很是多 UI 相关的扩展功能都作在框架内,而 Alamofire 的作法则是放在另外的扩展库中。网络

比方 AlamofireImageAlamofireNetworkActivityIndicator闭包

而 MBNetwork 就可以当作是 Alamofire 的一个扩展库,因此,MBNetwork 很是大程度上遵循了 Alamofire 接口的设计规范。一方面。减小了 MBNetwork 的学习成本,还有一方面。从我的角度来看。Alamofire 确实有很是多特别值得借鉴的地方。app

POP

首先固然是 POP 啦,Alamofire 大量运用了 protocol + extension 的实现方式。

enum

作为检验写 Swift 姿式正确与否的重要指标。Alamofire 固然不会缺。

链式调用

这是让 Alamofire 成为一个优雅的网络框架的重要缘由之中的一个。这一点 MBNetwork 也进行了全然的 Copy。

@discardableResult

在 Alamofire 全部带返回值的方法前面。都会有这么一个标签,事实上做用很是easy,因为在 Swift 中,返回值假设没有被使用,Xcode 会产生告警信息。加上这个标签以后,表示这种方法的返回值就算没有被使用,也不产生告警。

固然还有 ObjectMapper

引入 ObjectMapper 很是大一部分缘由是需要作错误和成功提示。

因为仅仅有解析服务端的错误信息节点才干知道返回结果是否正确,因此咱们引入 ObjectMapper 来作 JSON 解析。 而仅仅作 JSON 解析的缘由是眼下主流的服务端client数据交互格式是 JSON。

这里需要提到的就是另一个 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 採用了最后一种方法。

缘由很是easy。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 的实现就完毕了,使用方法很是easy。仅仅需要设置类型遵循 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 方法。这种方法实现的功能很是easy,就是返回 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 一致。

结合网络请求

至此,载入相关协议的定义和默认实现都已经完毕。现在需要作的就是把载入和网络请求结合起来。事实上很是easy。以前 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)

你会发现全部的东西都是可以本身定义的,而且使用起来仍然很是easy。

如下是利用 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 协议的泛型错误解析对象。

    传入这个对象到 AlamofireObjectMapper 的 responseObject 方法中就能够得到服务端返回的错误信息。

  • warn:遵循 MBWarnable 协议的错误展现对象。
  • completionHandler:返回结果正确时调用的闭包。业务层通常经过这个闭包来作特殊错误码处理。

作了例如如下的事情:

  • 经过 Alamofire 的 response 方法获取非业务错误信息,假设存在,则调用 warnshow 方法展现错误信息,这里你们可能会有点疑惑:为何可以把 String 当作 MBErrorable 传入到 show 方法中?这是因为咱们作了如下的事情:

    extension String: MBErrorable { public var message: String?

    { return self } }
  • 经过 AlamofireObjectMapper 的 responseObject 方法获取到服务端返回的错误信息,推断返回的错误码是否包括在 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 协议的泛型错误解析对象。因为假设服务端的返回结果是错的,则不该该提示成功。

仍是经过 AlamofireObjectMapper 的 responseObject 方法获取到服务端返回的错误信息。推断返回的错误码是否包括在 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 的实现加入对其余第三方提示库的支持。

又一次请求

开发中……敬请期待

相关文章
相关标签/搜索