Alamofire源码解读系列(五)之结果封装(Result)



本篇讲解Result的封装

前言

有时候,我们会根据现实中的事物来对程序中的某个业务关系进行抽象,这句话很难理解。在Alamofire中,使用Response来描述请求后的结果。我们都知道Alamofire返回的数据可以经过特殊的处理,比如说序列化,那么我们应该如何在Response中获取到这些类型不同的数据呢?

假如说序列化后的数据是data,最直接的想法就是把data设置为Any类型,在实际用到的时候在进行判断,这也是最普通的一种开发思维。现在我们就要打破这种思维。我们需要封装一个对象,这个对象能够表达任何结果,这就用到了swift中的泛型。

接下来在讲解Result之后,会给出两个使用泛型的例子,第一个例子表达基本的网络封装思想,第二个表达基本的viewModel思想。

Result

/// Used to represent whether a request was successful or encountered an error.
///
/// - success: The request and all post processing operations were successful resulting in the serialization of the
///            provided associated value.
///
/// - failure: The request encountered an error resulting in a failure. The associated values are the original data
///            provided by the server as well as the error that caused the failure.
public enum Result<Value> {
    case success(Value)
    case failure(Error)

}

关于如何描述结果,有两种可能,不是成功就是失败,因此考虑使用枚举。在Alamofire源码解读系列(二)之错误处理(AFError)这篇文章中我已经详细的讲解了枚举的使用方法。在上边的代码中,对枚举的每个子选项都做了值关联。

大家注意,泛型的写法是类似这样的:

struct CellConfigurator<Cell> where Cell: Updatable, Cell: UITableViewCell {
}

上边代码中的Cell必须符合后边给出的两个条件才行,这种用法是给泛型增加了条件限制,这种用法还有另外一种方式,看下边的代码:

 func send<T: Request>(_ r: T, handler: @escaping (T.Response?, String?) -> Void);

其实道理都差不多,都属于对泛型的灵活运用。

我们接着看看在Alamofire中是如何使用Result的。

  @discardableResult
    public func responseJSON(
        queue: DispatchQueue? = nil,
        options: JSONSerialization.ReadingOptions = .allowFragments,
        completionHandler: @escaping (DataResponse<Any>) -> Void)
        -> Self
    {
        return response(
            queue: queue,
            responseSerializer: DataRequest.jsonResponseSerializer(options: options),
            completionHandler: completionHandler
        )
    }

上边的这个函数的主要目的是把请求成功后的结果序列化为JSON,completionHandler函数的参数类型为DataResponse

那么问题来了,不是把数据解析成JSON了吗?为什么要返回Any类型呢?json本质上很类似于JavaScript中的对象和数组。JSONSerialization.jsonObject返回的类型是Any,这是因为解析后的数据有可能是数组,也有可能是字典。

字典:

{
    "people":[
        {"firstName":"Brett","lastName":"McLaughlin","email":"aaaa"},
        {"firstName":"Jason","lastName":"Hunter","email":"bbbb"},
        {"firstName":"Elliotte","lastName":"Harold","email":"cccc"}
    ]
}

数组:

[
    "a",
    "b",
    "c"
]

当然如果不是这两种格式的数据,使用JSONSerialization.jsonObject解析会抛出异常。

到这里我们就大概对这个Result有了一定的了解,下边的代码给result添加了一些属性,主要目的是使用起来更方便:

   /// Returns `true` if the result is a success, `false` otherwise.
    public var isSuccess: Bool {
        switch self {
        case .success:
            return true
        case .failure:
            return false
        }
    }

    /// Returns `true` if the result is a failure, `false` otherwise.
    public var isFailure: Bool {
        return !isSuccess
    }

    /// Returns the associated value if the result is a success, `nil` otherwise.
    public var value: Value? {
        switch self {
        case .success(let value):
            return value
        case .failure:
            return nil
        }
    }

    /// Returns the associated error value if the result is a failure, `nil` otherwise.
    public var error: Error? {
        switch self {
        case .success:
            return nil
        case .failure(let error):
            return error
        }
    }

当然,为了打印更加详细的信息,使Result实现了CustomStringConvertibleCustomDebugStringConvertible协议 :

// MARK: - CustomStringConvertible

extension Result: CustomStringConvertible {
    /// The textual representation used when written to an output stream, which includes whether the result was a
    /// success or failure.
    public var description: String {
        switch self {
        case .success:
            return "SUCCESS"
        case .failure:
            return "FAILURE"
        }
    }
}

// MARK: - CustomDebugStringConvertible

extension Result: CustomDebugStringConvertible {
    /// The debug textual representation used when written to an output stream, which includes whether the result was a
    /// success or failure in addition to the value or error.
    public var debugDescription: String {
        switch self {
        case .success(let value):
            return "SUCCESS: \(value)"
        case .failure(let error):
            return "FAILURE: \(error)"
        }
    }
}

总起来说,Result是一个比较简单的封装。

基于泛型的网络封装

在实际的开发工作中,我们使用Alamofire发送请求,获取服务器的数据,往往会对其进行二次封装,在这里,我讲解一个封装的例子,内容来自面向协议编程与 Cocoa 的邂逅

  1. 我们需要一个协议,这个协议提供一个函数,目的是把Data转换成实现该协议的对象本身。注意我们在这时候是不知道这个对象的类型的,为了适配更多的类型,这个对象暂时设计为泛型,因此协议中的函数应该是静态函数

    protocol Decodable {
        static func parse(data: Data) -> Self?
    }
  2. 封装请求,同样采用协议的方式

    public enum JZGHTTPMethod: String {
        case options = "OPTIONS"
        case get     = "GET"
        case head    = "HEAD"
        case post    = "POST"
        case put     = "PUT"
        case patch   = "PATCH"
        case delete  = "DELETE"
        case trace   = "TRACE"
        case connect = "CONNECT"
    
    }
    
    protocol Request {
    
        var path: String { get }
        var privateHost: String? { get }
    
        var HTTPMethod: JZGHTTPMethod { get }
        var timeoutInterval: TimeInterval { get }
        var parameter: [String: Any]? { get }
    
        associatedtype Response: Decodable
    }
  3. 封装发送端,同样采用协议的方式

    protocol Client {
    
        var host: String { get }
    
        func send<T: Request>(_ r: T, handler: @escaping (T.Response?, String?) -> Void);
    }
  4. 只要是实现了Client协议的对象,就有能力发送请求,在这里Alamofire是作为中间层存在的,只提供请求能力,可以随意换成其他的中间能力层

    struct AlamofireClient: Client {
    
        public static let `default` = { AlamofireClient() }()
    
        public enum HostType: String {
            case sandbox = "https://httpbin.org/post"
        }
    
        /// Base host URL
        var host: String = HostType.sandbox.rawValue
    
        func send<T : Request>(_ r: T, handler: @escaping (T.Response?, String?) -> Void) {
    
            let url = URL(string: r.privateHost ?? host.appending(r.path))!
    
            let sessionManager = Alamofire.SessionManager.default
            sessionManager.session.configuration.timeoutIntervalForRequest = r.timeoutInterval
    
            Alamofire.request(url, method: HTTPMethod(rawValue: r.HTTPMethod.rawValue)!,
                              parameters: r.parameter,
                              encoding: URLEncoding.default,
                              headers: nil)
                .response { (response) in
    
                    if let data = response.data, let res = T.Response.parse(data: data) {
    
                        handler(res, nil)
    
                    }else {
    
                        handler(nil, response.error?.localizedDescription)
                    }
            }
        }
    
    }

封装完成之后,我们来使用一下上边封装的功能:

  1. 创建一个TestRequest.swift文件,内部代码为:

    struct TestRequest: Request {
        let name: String
        let userId: String
    
        var path: String {
            return ""
        }
    
        var privateHost: String? {
            return nil
        }
    
        var timeoutInterval: TimeInterval {
            return 20.0
        }
    
        var HTTPMethod: JZGHTTPMethod {
            return .post
        }
    
        var parameter: [String : Any]? {
            return ["name" : name,
                    "userId" : userId]
        }
    
        typealias Response = TestResult
    }
  2. 创建TestResult.swift文件,内部代码为:

    struct TestResult {
        var origin: String
    }
    
    extension TestResult: Decodable {
        static func parse(data: Data) -> TestResult? {
            do {
               let dic = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
    
                guard let dict = dic as? Dictionary<String, Any> else {
                    return nil
                }
                return TestResult(origin: dict["origin"] as! String)
            }catch {
                return nil
            }
        }
    }
  3. 发送请求

    let request = TestRequest(name: "mama", userId: "12345");
    AlamofireClient.default.send(request) { (response, error) in
        print(response)
    }

对网络的基本封装就到此为止了 ,这里的Result可以是任何类型的对象,比如说User,可以通过上边的方法,直接解析成User对象。

基于泛型的cell封装

这种设计通常应用在MVVM之中,我们看下边的代码:

  1. 定义一个协议,这个协议提供一个函数,函数会提供一个参数,这个参数就是viewModel。cell只要实现了这个协议,就能够通过这个参数拿到viewModel,然后根据viewModel来配置自身控件的属性。

    protocol Updatable: class {
    
        associatedtype ViewData
    
        func update(viewData: ViewData)
    }
  2. 再定义一个协议,这个协议需要表示cell的一些信息,比如reuseIdentifier,cellClass,同时,这个协议还需要提供一个方法,赋予cell适配器更新cell的能力

    protocol CellConfiguratorType {
    
        var reuseIdentifier: String { get }
        var cellClass: AnyClass { get }
    
        func update(cell: UITableViewCell)
    }
  3. 创建CellConfigurator,这个CellConfigurator必须绑定一个viewData,这个viewData通过Updatable协议中的方法传递给cell

    struct CellConfigurator<Cell> where Cell: Updatable, Cell: UITableViewCell {
    
        let viewData: Cell.ViewData
        let reuseIdentifier: String = NSStringFromClass(Cell.self)
        let cellClass: AnyClass = Cell.self
    
        func update(cell: UITableViewCell) {
            if let cell = cell as? Cell {
                cell.update(viewData: viewData)
            }
        }
    }

    万变不离其宗啊,我们在请求到数据之后,需要把数据转变成CellConfigurator,也就是在数组中存放的是CellConfigurator类型的数据。

看看使用示例:

  1. 创建数组

     let viewController = ConfigurableTableViewController(items: [
                CellConfigurator<TextTableViewCell>(viewData: TextCellViewData(title: "Foo")),
                CellConfigurator<ImageTableViewCell>(viewData: ImageCellViewData(image: UIImage(named: "og")!)),
                CellConfigurator<ImageTableViewCell>(viewData: ImageCellViewData(image: UIImage(named: "GoogleLogo")!)),
                CellConfigurator<TextTableViewCell>(viewData: TextCellViewData(title: "Bar")),
                ])
  2. 注册cell

      func registerCells() {
            for cellConfigurator in items {
                tableView.register(cellConfigurator.cellClass, forCellReuseIdentifier: cellConfigurator.reuseIdentifier)
            }
        }
  3. 配置cell

       func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cellConfigurator = items[(indexPath as NSIndexPath).row]
            let cell = tableView.dequeueReusableCell(withIdentifier: cellConfigurator.reuseIdentifier, for: indexPath)
            cellConfigurator.update(cell: cell)
            return cell
        }

这个cell封装思想出自这里https://github.com/fastred/ConfigurableTableViewController

总结

上边两个例子,我解释的并不是很详细,只需要打开源码,仔细琢磨琢磨就能体会到里边的妙处,如有问题,可以留言。

本篇讲解Result的封装

前言

有时候,我们会根据现实中的事物来对程序中的某个业务关系进行抽象,这句话很难理解。在Alamofire中,使用Response来描述请求后的结果。我们都知道Alamofire返回的数据可以经过特殊的处理,比如说序列化,那么我们应该如何在Response中获取到这些类型不同的数据呢?

假如说序列化后的数据是data,最直接的想法就是把data设置为Any类型,在实际用到的时候在进行判断,这也是最普通的一种开发思维。现在我们就要打破这种思维。我们需要封装一个对象,这个对象能够表达任何结果,这就用到了swift中的泛型。

接下来在讲解Result之后,会给出两个使用泛型的例子,第一个例子表达基本的网络封装思想,第二个表达基本的viewModel思想。

Result

/// Used to represent whether a request was successful or encountered an error.
///
/// - success: The request and all post processing operations were successful resulting in the serialization of the
///            provided associated value.
///
/// - failure: The request encountered an error resulting in a failure. The associated values are the original data
///            provided by the server as well as the error that caused the failure.
public enum Result<Value> {
    case success(Value)
    case failure(Error)

}

关于如何描述结果,有两种可能,不是成功就是失败,因此考虑使用枚举。在Alamofire源码解读系列(二)之错误处理(AFError)这篇文章中我已经详细的讲解了枚举的使用方法。在上边的代码中,对枚举的每个子选项都做了值关联。

大家注意,泛型的写法是类似这样的:

struct CellConfigurator<Cell> where Cell: Updatable, Cell: UITableViewCell {
}

上边代码中的Cell必须符合后边给出的两个条件才行,这种用法是给泛型增加了条件限制,这种用法还有另外一种方式,看下边的代码:

 func send<T: Request>(_ r: T, handler: @escaping (T.Response?, String?) -> Void);

其实道理都差不多,都属于对泛型的灵活运用。

我们接着看看在Alamofire中是如何使用Result的。

  @discardableResult
    public func responseJSON(
        queue: DispatchQueue? = nil,
        options: JSONSerialization.ReadingOptions = .allowFragments,
        completionHandler: @escaping (DataResponse<Any>) -> Void)
        -> Self
    {
        return response(
            queue: queue,
            responseSerializer: DataRequest.jsonResponseSerializer(options: options),
            completionHandler: completionHandler
        )
    }

上边的这个函数的主要目的是把请求成功后的结果序列化为JSON,completionHandler函数的参数类型为DataResponse

那么问题来了,不是把数据解析成JSON了吗?为什么要返回Any类型呢?json本质上很类似于JavaScript中的对象和数组。JSONSerialization.jsonObject返回的类型是Any,这是因为解析后的数据有可能是数组,也有可能是字典。

字典:

{
    "people":[
        {"firstName":"Brett","lastName":"McLaughlin","email":"aaaa"},
        {"firstName":"Jason","lastName":"Hunter","email":"bbbb"},
        {"firstName":"Elliotte","lastName":"Harold","email":"cccc"}
    ]
}

数组:

[
    "a",
    "b",
    "c"
]

当然如果不是这两种格式的数据,使用JSONSerialization.jsonObject解析会抛出异常。

到这里我们就大概对这个Result有了一定的了解,下边的代码给result添加了一些属性,主要目的是使用起来更方便:

   /// Returns `true` if the result is a success, `false` otherwise.
    public var isSuccess: Bool {
        switch self {
        case .success:
            return true
        case .failure:
            return false
        }
    }

    /// Returns `true` if the result is a failure, `false` otherwise.
    public var isFailure: Bool {
        return !isSuccess
    }

    /// Returns the associated value if the result is a success, `nil` otherwise.
    public var value: Value? {
        switch self {
        case .success(let value):
            return value
        case .failure:
            return nil
        }
    }

    /// Returns the associated error value if the result is a failure, `nil` otherwise.
    public var error: Error? {
        switch self {
        case .success:
            return nil
        case .failure(let error):
            return error
        }
    }

当然,为了打印更加详细的信息,使Result实现了CustomStringConvertibleCustomDebugStringConvertible协议 :

// MARK: - CustomStringConvertible

extension Result: CustomStringConvertible {
    /// The textual representation used when written to an output stream, which includes whether the result was a
    /// success or failure.
    public var description: String {
        switch self {
        case .success:
            return "SUCCESS"
        case .failure:
            return "FAILURE"
        }
    }
}

// MARK: - CustomDebugStringConvertible

extension Result: CustomDebugStringConvertible {
    /// The debug textual representation used when written to an output stream, which includes whether the result was a
    /// success or failure in addition to the value or error.
    public var debugDescription: String {
        switch self {
        case .success(let value):
            return "SUCCESS: \(value)"
        case .failure(let error):
            return "FAILURE: \(error)"
        }
    }
}

总起来说,Result是一个比较简单的封装。

基于泛型的网络封装

在实际的开发工作中,我们使用Alamofire发送请求,获取服务器的数据,往往会对其进行二次封装,在这里,我讲解一个封装的例子,内容来自面向协议编程与 Cocoa 的邂逅

  1. 我们需要一个协议,这个协议提供一个函数,目的是把Data转换成实现该协议的对象本身。注意我们在这时候是不知道这个对象的类型的,为了适配更多的类型,这个对象暂时设计为泛型,因此协议中的函数应该是静态函数

    protocol Decodable {
        static func parse(data: Data) -> Self?
    }
  2. 封装请求,同样采用协议的方式

    public enum JZGHTTPMethod: String {
        case options = "OPTIONS"
        case get     = "GET"
        case head    = "HEAD"
        case post    = "POST"
        case put     = "PUT"
        case patch   = "PATCH"
        case delete  = "DELETE"
        case trace   = "TRACE"
        case connect = "CONNECT"
    
    }
    
    protocol Request {
    
        var path: String { get }
        var privateHost: String? { get }
    
        var HTTPMethod: JZGHTTPMethod { get }
        var timeoutInterval: TimeInterval { get }
        var parameter: [String: Any]? { get }
    
        associatedtype Response: Decodable
    }
  3. 封装发送端,同样采用协议的方式

    protocol Client {
    
        var host: String { get }
    
        func send<T: Request>(_ r: T, handler: @escaping (T.Response?, String?) -> Void);
    }
  4. 只要是实现了Client协议的对象,就有能力发送请求,在这里Alamofire是作为中间层存在的,只提供请求能力,可以随意换成其他的中间能力层

    struct AlamofireClient: Client {
    
        public static let `default` = { AlamofireClient() }()
    
        public enum HostType: String {
            case sandbox = "https://httpbin.org/post"
        }
    
        /// Base host URL
        var host: String = HostType.sandbox.rawValue
    
        func send<T : Request>(_ r: T, handler: @escaping (T.Response?, String?) -> Void) {
    
            let url = URL(string: r.privateHost ?? host.appending(r.path))!
    
            let sessionManager = Alamofire.SessionManager.default
            sessionManager.session.configuration.timeoutIntervalForRequest = r.timeoutInterval
    
            Alamofire.request(url, method: HTTPMethod(rawValue: r.HTTPMethod.rawValue)!,
                              parameters: r.parameter,
                              encoding: URLEncoding.default,
                              headers: nil)
                .response { (response) in
    
                    if let data = response.data, let res = T.Response.parse(data: data) {
    
                        handler(res, nil)
    
                    }else {
    
                        handler(nil, response.error?.localizedDescription)
                    }
            }
        }
    
    }

封装完成之后,我们来使用一下上边封装的功能:

  1. 创建一个TestRequest.swift文件,内部代码为:

    struct TestRequest: Request {
        let name: String
        let userId: String
    
        var path: String {
            return ""
        }
    
        var privateHost: String? {
            return nil
        }
    
        var timeoutInterval: TimeInterval {
            return 20.0
        }
    
        var HTTPMethod: JZGHTTPMethod {
            return .post
        }
    
        var parameter: [String : Any]? {
            return ["name" : name,
                    "userId" : userId]
        }
    
        typealias Response = TestResult
    }
  2. 创建TestResult.swift文件,内部代码为:

    struct TestResult {
        var origin: String
    }
    
    extension TestResult: Decodable {
        static func parse(data: Data) -> TestResult? {
            do {
               let dic = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
    
                guard let dict = dic as? Dictionary<String, Any> else {
                    return nil
                }
                return TestResult(origin: dict["origin"] as! String)
            }catch {
                return nil
            }
        }
    }
  3. 发送请求

    let request = TestRequest(name: "mama", userId: "12345");
    AlamofireClient.default.send(request) { (response, error) in
        print(response)
    }

对网络的基本封装就到此为止了 ,这里的Result可以是任何类型的对象,比如说User,可以通过上边的方法,直接解析成User对象。

基于泛型的cell封装

这种设计通常应用在MVVM之中,我们看下边的代码:

  1. 定义一个协议,这个协议提供一个函数,函数会提供一个参数,这个参数就是viewModel。cell只要实现了这个协议,就能够通过这个参数拿到viewModel,然后根据viewModel来配置自身控件的属性。

    protocol Updatable: class {
    
        associatedtype ViewData
    
        func update(viewData: ViewData)
    }
  2. 再定义一个协议,这个协议需要表示cell的一些信息,比如reuseIdentifier,cellClass,同时,这个协议还需要提供一个方法,赋予cell适配器更新cell的能力

    protocol CellConfiguratorType {
    
        var reuseIdentifier: String { get }
        var cellClass: AnyClass { get }
    
        func update(cell: UITableViewCell)
    }
  3. 创建CellConfigurator,这个CellConfigurator必须绑定一个viewData,这个viewData通过Updatable协议中的方法传递给cell

    struct CellConfigurator<Cell> where Cell: Updatable, Cell: UITableViewCell {
    
        let viewData: Cell.ViewData
        let reuseIdentifier: String = NSStringFromClass(Cell.self)
        let cellClass: AnyClass = Cell.self
    
        func update(cell: UITableViewCell) {
            if let cell = cell as? Cell {
                cell.update(viewData: viewData)
            }
        }
    }

    万变不离其宗啊,我们在请求到数据之后,需要把数据转变成CellConfigurator,也就是在数组中存放的是CellConfigurator类型的数据。

看看使用示例:

  1. 创建数组

     let viewController = ConfigurableTableViewController(items: [
                CellConfigurator<TextTableViewCell>(viewData: TextCellViewData(title: "Foo")),
                CellConfigurator<ImageTableViewCell>(viewData: ImageCellViewData(image: UIImage(named: "og")!)),
                CellConfigurator<ImageTableViewCell>(viewData: ImageCellViewData(image: UIImage(named: "GoogleLogo")!)),
                CellConfigurator<TextTableViewCell>(viewData: TextCellViewData(title: "Bar")),
                ])
  2. 注册cell

      func registerCells() {
            for cellConfigurator in items {
                tableView.register(cellConfigurator.cellClass, forCellReuseIdentifier: cellConfigurator.reuseIdentifier)
            }
        }
  3. 配置cell

       func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cellConfigurator = items[(indexPath as NSIndexPath).row]
            let cell = tableView.dequeueReusableCell(withIdentifier: cellConfigurator.reuseIdentifier, for: indexPath)
            cellConfigurator.update(cell: cell)
            return cell
        }

这个cell封装思想出自这里https://github.com/fastred/ConfigurableTableViewController

总结

上边两个例子,我解释的并不是很详细,只需要打开源码,仔细琢磨琢磨就能体会到里边的妙处,如有问题,可以留言。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值