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

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

须要干些啥

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

  • 怎样在任何位置发起网络请求。
  • 表单创建。

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

  • 载入遮罩。

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

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

  • 载入进度展示。

    下载上传图片等资源时提示用户当前进度。

  • 断点续传。

    下载上传图片等资源错误发生时能够在之前已完毕部分的基础上继续操作,这个 Alamofire 能够支持。

  • 数据解析。

    由于眼下主流服务端和client数据交换採用的格式是 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 的做法则是放在另外的扩展库中。比方 AlamofireImageAlamofireNetworkActivityIndicator

而 MBNetwork 就能够当做是 Alamofire 的一个扩展库,所以,MBNetwork 非常大程度上遵循了 Alamofire 接口的设计规范。

一方面。减少了 MBNetwork 的学习成本,还有一方面。从个人角度来看。Alamofire 确实有非常多特别值得借鉴的地方。

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 上显示载入遮罩

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbW1vYWF5/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" title="">

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 的实现加入对其他第三方提示库的支持。

又一次请求

开发中……敬请期待

时间: 2024-10-18 19:32:04

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

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

最近开源了一个面向协议设计的网络请求库 MBNetwork,基于 Alamofire 和 ObjectMapper 实现,目的是简化业务层的网络请求操作. 需要干些啥 对于大部分 App 而言,业务层做一次网络请求通常关心的问题有如下几个: 如何在任意位置发起网络请求. 表单创建.包含请求地址.请求方式(GET/POST/--).请求头等-- 加载遮罩.目的是阻塞 UI 交互,同时告知用户操作正在进行.比如提交表单时在提交按钮上显示 "菊花",同时使其失效. 加载进度展示.下载上传图片

自己动手写一个iOS 网络请求库的三部曲[转]

代码示例:https://github.com/johnlui/Swift-On-iOS/blob/master/BuildYourHTTPRequestLibrary 开源项目:Pitaya,适合大文件上传的 HTTP 请求库:https://github.com/johnlui/Pitaya 本系列文章中,我们将尝试使用 NSURLSession 技术构建一个自己的网络请求库. NSURLSession 简介 NSURLSession 是 iOS7 引入的新网络请求接口,在 WWDC2013

动手造轮子:实现一个简单的依赖注入(一)

动手造轮子:实现一个简单的依赖注入(一) Intro 在上一篇文章中主要介绍了一下要做的依赖注入的整体设计和大概编程体验,这篇文章要开始写代码了,开始实现自己的依赖注入框架. 类图 首先来温习一下上次提到的类图 服务生命周期 服务生命周期定义: public enum ServiceLifetime : sbyte { /// <summary> /// Specifies that a single instance of the service will be created. /// &

动手造轮子:实现一个简单的 EventBus

动手造轮子:实现一个简单的 EventBus Intro EventBus 是一种事件发布订阅模式,通过 EventBus 我们可以很方便的实现解耦,将事件的发起和事件的处理的很好的分隔开来,很好的实现解耦. 微软官方的示例项目 EShopOnContainers 也有在使用 EventBus . 这里的 EventBus 实现也是参考借鉴了微软 eShopOnContainers 项目. EventBus 处理流程: 微服务间使用 EventBus 实现系统间解耦: 借助 EventBus 我

造轮子:新建一个属于自己的String类

练习造轮子,新建一个属于自己的MyString类 首先来开启检测内存泄漏的函数 在main里添加 _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF); 开启内存泄漏检测 int main() { _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF); int *p = new int; return

重复造轮子,编写一个轻量级的异步写日志的实用工具类(LogAsyncWriter)

一说到写日志,大家可能推荐一堆的开源日志框架,如:Log4Net.NLog,这些日志框架确实也不错,比较强大也比较灵活,但也正因为又强大又灵活,导致我们使用他们时需要引用一些DLL,同时还要学习各种用法及配置文件,这对于有些小工具.小程序.小网站来说,有点“杀鸡焉俺用牛刀”的感觉,而且如果对这些日志框架不了解,可能输出来的日志性能或效果未毕是与自己所想的,鉴于这几个原因,我自己重复造轮子,编写了一个轻量级的异步写日志的实用工具类(LogAsyncWriter),这个类还是比较简单的,实现思路也很

线程安全-一个VC下多个网络请求

一.线程安全变量控制显示隐藏loading框 问题描写叙述: 同一页面有两个异步网络请求,第一个请求開始,loading旋转.第二个请求開始loading旋转.第一个结束,loading停止旋转,但是这时第二个请求还没有结束.然后loading就结束了,于是问题就来了. 解决方式: 二.由上面问题引申出的问题: 1. #import <libkern/OSAtomic.h> 这段话是从网上copy过来的.总结了一下原子操作的作用. 可是文中提到的osbase.h文件找不到.可能是由于版本号升级

【造轮子】打造一个简单的万能Excel读写工具

大家工作或者平时是不是经常遇到要读写一些简单格式的Excel? shit!~很蛋疼,因为之前吹牛,就搞了个这东西,还算是挺实用,和大家分享下. 厌烦了每次搞简单类型的Excel读写?不怕~来,喜欢流式操作?来~,喜欢用lambda(虽然java的比较蛋疼~),来~看这个~ 哈哈,如果你用的不是java8~~没问题,那就默默地用老方式匿名类来实现这些功能吧,但是这并不妨碍您的使用哈哈~~~ 更新多次~希望大家也能够集思广益~ github地址:https://github.com/MatrixSe

谈谈自己造轮子

写下这篇文章,主要是对我近段时间工作的反思. 为啥要造轮子 对于一些程序员来说,喜欢自己造轮子可算是一个很平常的事情,我想可能有如下原因: 对于一些小的功能,不需要借助外部库,直接能够自己写完搞定. 对于一些大的功能,很多外部库不能很好的与自己项目整合,有时候还不如自己写一个. 有时候即使能用的外部库,因为程序员相轻的思想,就觉得自己写的nb,不用. 还有可能就是想深入学习某一个知识点,自己动手造一个. 我不觉得造轮子不好,曾今很长一段时间我都认为造轮子是体现自己能力很好的一种方式,但是现在越来