结果 Alamofire

假如说序列化后的数据是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)

}

关于如何描述结果
,有两种可能,不是成功就是失败,因此考虑使用枚举。在上边的代码中,对枚举的每个子选项都做了值关联。
大家注意,泛型的写法是类似这样的:<T>,在<和>之间声明一种类型,这个T知识象征性的,在赋值的时候,可以是任何类型。还有一种用法,看下边的代码:

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

字典:

{
    "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实现了CustomStringConvertible和CustomDebugStringConvertible协议 :

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

总结

由于知识水平有限,如有错误,还望指出

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容

  • 本篇讲解Result的封装 前言 有时候,我们会根据现实中的事物来对程序中的某个业务关系进行抽象,这句话很难理解。...
    老马的春天阅读 3,725评论 2 20
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,036评论 29 470
  • iOS网络架构讨论梳理整理中。。。 其实如果没有APIManager这一层是没法使用delegate的,毕竟多个单...
    yhtang阅读 5,098评论 1 23
  • 白色的风筝 白色的风筝 我想着 描上了蝴蝶后 应该是天空中最闪亮的一个吧 可刚拿着笔勾勒时 一阵风 不期而至 这风...
    时间煮了我阅读 365评论 0 0
  • 几家欢喜,几家愁。 我有位好友,没有考上三笔,也没有考上二笔,过程太艰辛,但是经过了这么多努力,他考上了高翻。是考...
    brekker阅读 110评论 0 0