转载

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

CSDN移动将持续为您优选移动开发的精华内容,共同探讨移动开发的技术热点话题,涵盖移动应用、开发工具、移动游戏及引擎、智能硬件、物联网等方方面面。如果您想投稿、参与内容翻译工作,或寻求近匠报道,请发送邮件至tangxy#csdn.net(请把#改成@)。 

本文出自: Thoughtbot ,作者:Tony DiPasquale,译文出自: SwiftGG ,译者:shanks

苹果推出的名为“Swift”的全新编程语言,这让我们对未来iOS和OS X开发充满了期待与兴奋。人们纷纷开始使用Xcode Beta1版本来进行Swift开发,但是很快就发现解析JSON这一常见的操作在Swift中并不如在Objectitve-C中那样快捷和方便。

Swift是一门 静态类型 的语言,这意味我们不能简单地将对象赋值给一个特定类型的变量,并且让编译器相信这些对象就是我们所声明的那种类型。在Swift当中,编译器会进行检查,以确保我们不会意外地触发运行时错误。这使得我们可以依赖编译器来写出一些无Bug的代码,同时我们必须做许多额外的工作来使编译器不报错。在这篇文章当中,我将使用函数式思想和 泛型 来探讨如何编写易读高效的 JSON 解析代码。

请求用户(User)模型

我们要做的事就是将网络请求获得的数据解析成JSON。之前我们一直使用的是 NSJSONSerialization.JSONObjectWithData(NSData, Int, &NSError) 方法,这个方法返回一个可选的JSON数据类型,如果解析过程出错会得到NSError类型的数据。

在Objective-C当中,JSON的数据类型是一个可以包含任何其它数据类型的 NSDictionary 类型。 而在Swift当中,新的字典类型要求我们必须显式指定它所包含的数据的类型。JSON 数据被指定为 Dictionary<String, AnyObject> 类型。

这里使用 AnyObject 的原因是JSON的值有可能为S tringDoubleBoolArrayDictionary 或者 null 。当我们使用JSON来生成模型数据时,必须对每一个从JSON字典中获取到的值进行判断,以确保这个值与我们模型中属性的类型一致。

下面我们来看一个用户(user)的模型:

struct User {   let id: Int   let name: String   let email: String }

然后,来看一下对当前用户的请求和响应代码:

func getUser(request: NSURLRequest, callback: (User) -> ()) {   let task = NSURLSession.sharedSession().dataTaskWithRequest(request)   { data, urlResponse, error in     var jsonErrorOptional: NSError?     let jsonOptional: AnyObject! =      NSJSONSerialization.JSONObjectWithData(data,      options: NSJSONReadingOptions(0), error: &jsonErrorOptional)  if let json = jsonOptional as? Dictionary<String, AnyObject> {       if let id = json["id"] as AnyObject? as? Int {        // 在 beta5 中,存在一个 bug,所以我们首先要强行转换成 AnyObject?         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(user)           }         }       }     }   }   task.resume() }

在一长串的 if-let 语句之后,我们终于拿到 User 对象。可以想象一下,如果一个模型的属性很多,这些代码会有多丑。并且,这里我们没有进行错误处理,这意味着,只要其中一步出错我们就获取不到任何数据。最后并且最重要的一点是,我们必须对每个需要从网络 API 中获取的模型写一遍类似上面这样的代码,这将会导致很多重复代码。

在对代码进行重构之前,让我们先对JSON的几种类型定义别名,以使之后的代码看起来更简洁。

typealias JSON = AnyObject typealias JSONDictionary = Dictionary<String, JSON> typealias JSONArray = Array<JSON>

重构:添加错误处理

首先,我们将通过学习第一个函数式编程的概念, Either<A, B> 类型 ,来对代码进行重构,以使其能进行错误处理。这可以使代码在正确的情况下返回用户对象,而在出错时返回一个错误对象。在Swift当中可以使用如下方法来实现 Either<A, B>

enum Either<A, B> {   case Left(A)   case Right(B) }

我们可以使用 Either<NSError, User> 作为传入回调的参数,这样调用者便可以直接处理解析过的 User 对象或者错误。

func getUser(request: NSURLRequest, callback:      (Either<NSError, User>) -> ()) {   let task = NSURLSession.sharedSession().dataTaskWithRequest(request)    { data, urlResponse, error in     // 如果响应返回错误,我们将把错误发送给回调     if let err = error {       callback(.Left(err))       return     }          var jsonErrorOptional: NSError?     let jsonOptional: JSON! =      NSJSONSerialization.JSONObjectWithData(data,      options: NSJSONReadingOptions(0), error: &jsonErrorOptional)          // 如果我们不能解析 JSON,就将发送回去一个错误     if let err = jsonErrorOptional {       callback(.Left(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(.Right(user))             return           }         }       }     }      // 如果我们不能解析所有的属性,就将发送回去一个错误     callback(.Left(NSError()))   }   task.resume() }

现在调用 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 继续操作   } }

改变不是很大,我们继续努力。

正文到此结束
Loading...