JSON

一段简单的JSON解析代码到底能重构几次?(3)

字号+ 作者:H5之家 来源:H5之家 2015-10-08 10:10 我要评论( )

现在调用getUser的地方可以直接使用Either,然后对接收到的用户对象进行处理,或者直接显示错误。 getUser(request) { either in switch either { case let .Left(error)://显示错误信息 case let .Right(user)://

现在调用getUser的地方可以直接使用Either,然后对接收到的用户对象进行处理,或者直接显示错误。

getUser(request) { either in switch either { case let .Left(error): //显示错误信息 case let .Right(user): //对user进行操作 } }

我们假设Left一直是NSError,这可以进一步简化代码。我们可以使用一个不同的类型Result<A>来保存我们需要的类型数据和错误信息。它的实现方式如下:

enum Result<A> { case Error(NSError) case Value(A) }

在当前的Swift版本(Beta 5)中,上面的Result类型会造成(译者注:事实上,在Swift 1.2中还是有错误)。Swift需要知道存储在enum当中数据的确切类型,可以通过创建一个静态类作为包装类型来解决这个问题:

final class Box<A> { let value: A init(_ value: A) { self.value = value } } enum Result<A> { case Error(NSError) case Value(Box<A>) }将Either替换为Result,代码将变成这样:func getUser(request: NSURLRequest, callback: (Result<User>) -> ()) { let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, urlResponse, error in // 如果响应返回错误,我们将把错误发送给回调 if let err = error { callback(.Error(err)) return } var jsonErrorOptional: NSError? let jsonOptional: JSON! = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions(0), error: &jsonErrorOptional) // 如果我们不能解析 JSON,就返回一个错误 if let err = jsonErrorOptional { callback(.Error(err)) return } if let json = jsonOptional as? JSONDictionary { if let id = json["id"] as AnyObject? as? Int { if let name = json["name"] as AnyObject? as? String { if let email = json["email"] as AnyObject? as? String { let user = User(id: id, name: name, email: email) callback(.Value(Box(user))) return } } } } // 如果我们不能解析所有的属性,就返回一个错误 callback(.Error(NSError())) } task.resume() }getUser(request) { result in switch result { case let .Error(error): // 显示错误信息 case let .Value(boxedUser): let user = boxedUser.value // 对 user 继续操作 } }改变不是很大,我们继续努力。

重构:消除多层嵌套

接下来,我们将为每个不同的类型创建一个JSON解析器来消灭掉那些丑陋的解析JSON的代码。在这个对象中我们只用到了String、Int和Dictionary三种类型,所以我们需要三个函数来对这三种类型进行解析。

func JSONString(object: JSON?) -> String? { return object as? String } func JSONInt(object: JSON?) -> Int? { return object as? Int } func JSONObject(object: JSON?) -> JSONDictionary? { return object as? JSONDictionary }现在,解析JSON的代码看起来应该是这样的:
if let json = JSONObject(jsonOptional) { if let id = JSONInt(json["id"]) { if let name = JSONString(json["name"]) { if let email = JSONString(json["email"]) { let user = User(id: id, name: name, email: email) } } } }

即使使用了这些函数,还是需要用到一大堆的if-let语句。函数式编程中的Monads、Applicative Functors以及Currying概念可以帮助我们来压缩这段代码。首先看看与Swift中的可选类型十分相似的Monad。Monad中有一个绑定(bind)运行符,这个运行符可以给一个可选类型绑定一个函数,这个函数接受一个非可选类型参数,并返回一个可选类型的返回值。如果第一个可选类型是.None这个运行符会返回.None,否则它会对这个可选类型进行解包,并使用绑定的函数调用解包后的数据。

infix operator >>> { associativity left precedence 150 } func >>><A, B>(a: A?, f: A -> B?) -> B? { if let x = a { return f(x) } else { return .None } }

在其他的函数式语言中,都是使用>>=来作为绑定(bind)运算符,但是在Swift中这个运算符被用于二进制位的移位操作,所以我们使用了>>>来作为替代。在JSON代码中使用这个操作符可以得到如下代码:

if let json = jsonOptional >>> JSONObject { if let id = json["id"] >>> JSONInt { if let name = json["name"] >>> JSONString { if let email = json["email"] >>> JSONString { let user = User(id: id, name: name, email: email) } } } }

接着就可以去掉解析函数里的可选参数:

func JSONString(object: JSON) -> String? { return object as? String } func JSONInt(object: JSON) -> Int? { return object as? Int } func JSONObject(object: JSON) -> JSONDictionary? { return object as? JSONDictionary }

Functors 有一个fmap运算符,可以在某些上下文中通过函数应用到解包后的值上面。Applicative Functors也有apply运算符,可以在某些上下文中通过解包后的函数应用到解包后的值上面。这里的上下文是一个包含了值的可选值。这就意味着我们可以使用一个能够带有多个非可选值的函数来连接多个可选值。如果所有的值都存在,.Some会得到可选值解包的结果。如果其中任何值是.None,我们将得到.None。可以在Swift中像下面这样定义这些运算符:

infix operator <^> { associativity left } // Functor's fmap (usually <>) infix operator <*> { associativity left } // Applicative's apply func <^><A, B>(f: A -> B, a: A?) -> B? { if let x = a { return f(x) } else { return .None } } func <*><A, B>(f: (A -> B)?, a: A?) -> B? { if let x = a { if let fx = f { return fx(x) } } return .None }

 

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

相关文章
  • iOS入门学习(json解析)

    iOS入门学习(json解析)

    2016-01-26 09:00

  • iOS中json解析出现的null,nil,NSNumber的问题,jsonnsnumber

    iOS中json解析出现的null,nil,NSNumber的问题,jsonnsnumber

    2016-01-17 15:03

  • iOS开发Post请求错误:Error Domain=NSCocoaErrorDomain Code=3840

    iOS开发Post请求错误:Error Domain=NSCocoaErrorDomain Code=3840 "

    2015-11-23 19:03

  • 叶玲童鞋的课程

    叶玲童鞋的课程

    2015-11-23 12:17

网友点评