CSDN移动将持续为您优选移动开发的精华内容,共同探讨移动开发的技术热点话题,涵盖移动应用、开发工具、移动游戏及引擎、智能硬件、物联网等方方面面。如果您想投稿、参与内容翻译工作,或寻求近匠报道,请发送邮件至tangxy#csdn.net(请把#改成@)。
本文出自: Thoughtbot ,作者:Tony DiPasquale,译文出自: SwiftGG ,译者:shanks
苹果推出的名为“Swift”的全新编程语言,这让我们对未来iOS和OS X开发充满了期待与兴奋。人们纷纷开始使用Xcode Beta1版本来进行Swift开发,但是很快就发现解析JSON这一常见的操作在Swift中并不如在Objectitve-C中那样快捷和方便。
Swift是一门 静态类型 的语言,这意味我们不能简单地将对象赋值给一个特定类型的变量,并且让编译器相信这些对象就是我们所声明的那种类型。在Swift当中,编译器会进行检查,以确保我们不会意外地触发运行时错误。这使得我们可以依赖编译器来写出一些无Bug的代码,同时我们必须做许多额外的工作来使编译器不报错。在这篇文章当中,我将使用函数式思想和 泛型 来探讨如何编写易读高效的 JSON 解析代码。
我们要做的事就是将网络请求获得的数据解析成JSON。之前我们一直使用的是 NSJSONSerialization.JSONObjectWithData(NSData, Int, &NSError)
方法,这个方法返回一个可选的JSON数据类型,如果解析过程出错会得到NSError类型的数据。
在Objective-C当中,JSON的数据类型是一个可以包含任何其它数据类型的 NSDictionary
类型。 而在Swift当中,新的字典类型要求我们必须显式指定它所包含的数据的类型。JSON 数据被指定为 Dictionary<String, AnyObject>
类型。
这里使用 AnyObject
的原因是JSON的值有可能为S tring
、 Double
、 Bool
、 Array
、 Dictionary
或者 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 继续操作 } }
改变不是很大,我们继续努力。