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

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

前言

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

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<Tool> {
        return curry(Tool.init)
            <^> json <| "id"
            <*> json <| "type"
            <*> json <| "name"
            <*> json <| "code"
            <*> json <|? "url"
            <*> json <|? "iconUrl"
    }
}
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中定义了

自定义运算符

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

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

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

<| 运算符

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

<^> json <| "id"

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

<|函数

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

public func decodedJSON(_ json: JSON, forKey key: String) -> Decoded<JSON> {
  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<JSON> {
  switch json {
  case .null: return .missingKey(key)
  default: return pure(json)
  }
}
public func pure<T>(_ x: T) -> Decoded<T> {
  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<U>`
    - returns: A value of type `Decoded<U>`
  */
  func flatMap<U>(_ f: (T) -> Decoded<U>) -> Decoded<U> {
    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<String>`.
    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<String> {
    switch json {
    case let .string(s): return pure(s)
    default: return .typeMismatch(expected: "String", actual: json)
    }
  }
}
extension Int: Decodable {
  /**
    Decode `JSON` into `Decoded<Int>`.
    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<Int> {
    switch json {
    case let .number(n): return pure(n.intValue)
    default: return .typeMismatch(expected: "Int", actual: json)
    }
  }
}
......

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

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的过程了。

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

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,019评论 4 62
  • 转载自:https://github.com/Tim9Liu9/TimLiu-iOS 目录 UI下拉刷新模糊效果A...
    袁俊亮技术博客阅读 11,906评论 9 105
  • 87. Scramble String: 区间dp加上memory search97. Interleaving ...
    健时总向乱中忙阅读 195评论 0 0
  • 每当听起“宁夏”这首歌,就会想起美丽的宁夏_我的故乡
    香橙的邂逅阅读 265评论 0 0
  • 沉痛与哀绝不应该在沉默中寂灭 欢乐与欣喜不应该在俗世中殆尽 若是两眼相悦的一望 能够激发你内心的呼唤 呼唤你心中最...
    素絢阅读 278评论 9 7