Argo+Curry+Runes完成网络数据转Model

最近我接触了一个新项目,这个项目是纯swift 开发。这激起了我对swift 的兴趣,

前言

之前都是拿swift 做一些demo ,没有用在项目中。这回可以大手大脚的开始写swift 。早就对swift 的函数式编程感兴趣。这回终于如愿所偿了。先说下。项目中用到的第三方库。
Toaster 弹出一些提示信息
SnapKit swift 中的约束库, 和objective-c中的masonry 语法非常相似。只要会用masonry的,这个库可以无脑上手。
Alamofire 这个不用说,swift 版的网络请求库
ReactiveCocoaReactiveSwift ,因为项目是MVVM 开发模式。所以这两个库是必须要有的。吐槽一句,这两个库用起来太方便了。

Argo+Curry+Runes完成网络数据转Model_第1张图片
Paste_Image.png

Result配合 ReactiveCocoaReactiveSwift。它俩天生就是一对
Argo+ Curry+ Runes 这三个库就是完成网络数据转换成模型的。也是今天要介绍的重点。

例子

好了,废话不多说。先来看一个现成的例子吧:

 guard let dataArr = tool["data"] as? [JSONData], dataArr.count > 0 else {
        return 
    }
    let tools = dataArr.flatMap { (dict: JSONData) -> Tool? in
        return decode(dict)
        }
public struct Tool {
    let id: String      
    let type: String          
    let name: String   
    let code: String   
    let url: String?   
    let iconUrl: String?
}
extension Tool: Decodable {
    public static func decode(_ json: JSON) -> Decoded {
        return curry(Tool.init)
            <^> json <| "id"
            <*> json <| "type"
            <*> json <| "name"
            <*> json <| "code"
            <*> json <|? "url"
            <*> json <|? "iconUrl"
    }
}
Argo+Curry+Runes完成网络数据转Model_第2张图片
What?

看完这个例子有什么感想(ps: 我第一次看见的时候感觉这什么东西,就这几句话就完成模型转换了。swift也太神奇了吧!)。看不懂没关系。下面我们一层一层的来解释这几行代码。

正文

1.首先解释下这个decode方法。这个是Decodable协议中的方法。自定义的数据结构只要遵循Decodable协议,实现decode方法就可以完成模型转换。

  1. JSON 参数。这个是argo中定义的数据结构。
public enum JSON {
  case object([String: JSON])
  case array([JSON])
  case string(String)
  case number(NSNumber)
  case bool(Bool)
  case null
}
public extension JSON {
  /**
    Transform an `Any` instance into `JSON`.
    This is used to move from a loosely typed object (like those returned from
    `NSJSONSerialization`) to the strongly typed `JSON` tree structure.
    - parameter json: A loosely typed object
  */
  init(_ json: Any) {
    switch json {
    case let v as [Any]:
      self = .array(v.map(JSON.init))
    case let v as [String: Any]:
      self = .object(v.map(JSON.init))
    case let v as String:
      self = .string(v)
    case let v as NSNumber:
      if v.isBool {
        self = .bool(v.boolValue)
      } else {
        self = .number(v)
      }
    default:
      self = .null
    }
  }
}

这是它的定义。可以看到,这个JSON 就是对网络数据的枚举化。JSON 可以适配所有的网络数据。
3.拿到网络数据后调用了下面

 let tools = dataArr.flatMap { (dict: JSONData) -> Tool? in
        return decode(dict)
        }

这句会调用Tool的decode方法,将网络数据dict 传入decode方法中,decode 会将网络数据转化成Tool对象(也有可能转换不成功返回nil),最后用flatMap对无效数据过滤。最后就得到了我们想要的模型了。所以所有的关键就是decode方法了。
4.先抛开curry 函数不说。我们先来看下

 <^> json <| "id"
 <*> json <| "type"

这两行是什么意思。
<^>,** <|<>* 都是自定义运算符。在Runes中定义了

Argo+Curry+Runes完成网络数据转Model_第3张图片
自定义运算符

写到这里有要说说swift 中的自定义运算符了, 链接。运算符要指定结合性和优先级。拿 RunesApplicativePrecedence来说这组运算符是左结合,优先级比 NilCoalescingPrecedence低,比 RunesAlternativePrecedence高,而 NilCoalescingPrecedence是swift 中定义的标准运算符组 ??所属的优先级。[swift中常用的运算符对应的优先级]( http://www.jianshu.com/p/4f025476701a)。而 RunesAlternativePrecedence又是自定义的运算符优先级组

precedencegroup RunesAlternativePrecedence {
  associativity: left
  higherThan: LogicalConjunctionPrecedence
  lowerThan: ComparisonPrecedence
}

这个优先级组比 比较运算符 低,比&&高。
所以 <^>&&高,比 ??低。
在来看<|

Argo+Curry+Runes完成网络数据转Model_第4张图片
<| 运算符

可见这个运算符比 RunesApplicativeSequencePrecedence高。而 RunesApplicativeSequencePrecedenceRunesApplicativePrecedence高。所以 <|<^>高。
所以

<^> json <| "id"

中先执行的是json <| "id"然后在执行的<^>
5.<|表达式执行过程:

Argo+Curry+Runes完成网络数据转Model_第5张图片
<|函数

这个函数会对每一个key 调用 decodedJSON方法,这个方法会对数据模型转换。如果成功会转换成 .success()。如果失败会变成 .typeMismatch或者 .missingKey

public func decodedJSON(_ json: JSON, forKey key: String) -> Decoded {
  switch json {
  case let .object(o): return guardNull(key, o[key] ?? .null)
  default: return .typeMismatch(expected: "Object", actual: json)
  }
}
private func guardNull(_ key: String, _ json: JSON) -> Decoded {
  switch json {
  case .null: return .missingKey(key)
  default: return pure(json)
  }
}
public func pure(_ x: T) -> Decoded {
  return .success(x)
}

最后得到一个decoded对象。调用A.decode方法。如果是success的会对数据解包。如果失败会返回一个错误。

public extension Decoded {
  /**
    Conditionally map a function over `self`, flattening the result.
    - If `self` is `.Failure`, the function will not be evaluated and this will
      return `.Failure`.
    - If `self` is `.Success`, the function will be applied to the unwrapped
      value.
    - parameter f: A transformation function from type `T` to type `Decoded`
    - returns: A value of type `Decoded`
  */
  func flatMap(_ f: (T) -> Decoded) -> Decoded {
    switch self {
    case let .success(value): return f(value)
    case let .failure(error): return .failure(error)
    }
  }
}

6.在argo库中有一个文件StandardTypes.swift这里面定义的对基本数据的decode方法。这就是完整的argo 从网络数据转换成模型的过程。有兴趣的可以看下argo 这个库的源码。Argo ,Rune。

extension String: Decodable {
  /**
    Decode `JSON` into `Decoded`.
    Succeeds if the value is a string, otherwise it returns a type mismatch.
    - parameter json: The `JSON` value to decode
    - returns: A decoded `String` value
  */
  public static func decode(_ json: JSON) -> Decoded {
    switch json {
    case let .string(s): return pure(s)
    default: return .typeMismatch(expected: "String", actual: json)
    }
  }
}
extension Int: Decodable {
  /**
    Decode `JSON` into `Decoded`.
    Succeeds if the value is a number that can be converted to an `Int`,
    otherwise it returns a type mismatch.
    - parameter json: The `JSON` value to decode
    - returns: A decoded `Int` value
  */
  public static func decode(_ json: JSON) -> Decoded {
    switch json {
    case let .number(n): return pure(n.intValue)
    default: return .typeMismatch(expected: "Int", actual: json)
    }
  }
}
......

7.说完了argo 在来看之前的代码。是不是明白了很多。但是还有一点。那就是Curry函数。那下面就来说道说道Curry函数吧。
Curry化技术是一种通过把多个参数填充到函数体中,实现将函数转换为一个新的经过简化的(使之接受的参数更少)函数的技术

Argo+Curry+Runes完成网络数据转Model_第6张图片
Curry函数部分定义

柯里化函数就是接受一个参数然后返回另一个带有参数的函数。可以看到Curry函数最终会调用 function(a, b), function(a, b,c)等方法。所以在最初的代码中。

 return curry(Tool.init)
            <^> json <| "id"
            <*> json <| "type"
            <*> json <| "name"
            <*> json <| "code"
            <*> json <|? "url"
            <*> json <|? "iconUrl"

转化后就是Tool.init(id,type,name,code,url,iconUrl) 方法。就是结构体自带的默认初始化方法。
所以到这里就全部解开了。argo+Curry+Runes 将网络数据转换成model的过程了。

你可能感兴趣的:(Argo+Curry+Runes完成网络数据转Model)