objc.io 推出一个新的谈话节目,一周一期,每期的主题固定,用 20 多分钟的时间去详述一个主题,很有意思,近期做个记录,内容比较简单就不翻译了,主要记录一下背后的思想。
一般网络层所做的工作无非就是发起请求,得到 NSData,然后进行 json 解析,接着映射成对应的 Model,最后传递给回调函数。比如我想得到一个填满 Episodes 类型的数组,但网络只能给你返回一个 id 类型,如果用传统的 OC 来写,这背后就涉及到复杂的类型转换,上面提到的每一步都可能出错。不过在 Swift 中我们有更好的办法。
创建了一个带泛型参数的 Resource<A>
结构体作为网络请求和 Model 交流的中间层:
Webservice 专门负责网络请求,它需要 Resource<A>
提供一些必要的网络请求的参数,并且将网络请求得到的数据(NSData)传递给 Resource<A>
的解析函数
final class Webservice { func load<A>(resource: Resource<A>, completion:(A?)->()) { NSURLSession.sharedSession().dataTaskWithURL(resource.url) { data, _, _ in let result = data.flatMap(resource.parse) completion(result) }.resume() } }
提供网络请求需要的一些参数和一个把 NSData 解析成 Model 的函数,我们使用泛型参数 A 来限定 Model 的类型。
struct Resource<A> { let url: NSURL let parse: NSData -> A? }
上面提到了 Resource<A>
结构体的两个主要功能:
我们可以直接在初始化里赋值:
extension Resource { init(url: NSURL, parseJSON: AnyObject -> A?) { self.url = url // 直接赋值一个 block 解析的实现 self.parse = { data in let json = try? NSJSONSerialization.JSONObjectWithData(data, options: []) return json.flatMap(parseJSON) } } }
重点来关注一下解析函数:前面说过 Resource 的作用是将网络请求到的 data 解析成 Model,因为 Resource 并不关心 Model 的具体类型,所以这里分为两步走,Resource 负责把 NSData 解析成 AnyObject,而剩下的事情交给具体的 Model 去映射。为了解耦采用了回调的方式将 json(AnyObject)传递给相关 Model 处理。
主要负责 Json 对象到 Model 的映射
typealias JSONDictionary = [String:AnyObject] struct Episode { let id :String let title: String } extension Episode { init?(dictionary: JSONDictionary) { guard let id = dictionary["id"] as? String, let title = dictionary["title"] as? String else {return nil} self.id = id self.title = title } }
上面说了 Resource 只负责从 NSData 解析到 AnyObject( NSData -> AnyObject
),剩下的 AnyObject -> A?
由 Model 层接管,我们可以初始化一个 Resource 类型常量,传入 AnyObject -> A?
的实现
extension Episode { static let all = Resource<[Episode]>(url: url) { json in if let dictionarys = json as? [JSONDictionary] { return dictionarys.flatMap(Episode.init) } return nil } }
load<A>(resource: Resource<A>, completion:(A?)->())
, Resource 的 let parse: NSData -> A?
,利用泛型约束编译器可以在编译期就发现错误,而不用等到运行时崩溃了才去查错。 谈话到此结束,让我们发散一下思维,其实还可以为 Resource 添加更多的网络请求参数,比如:
struct Resource<A> { let path: String let method : Method let requestBody: NSData? let headers : [String:String] let parse: NSData -> A? }
添加独立的错误处理模块
public enum Reason { case CouldNotParseJSON case NoData case NoSuccessStatusCode(statusCode: Int) case Other(NSError?) } struct Failure { static func defaultFailureHandler(failureReason: Reason, data: NSData?) { let string = NSString(data: data!, encoding: NSUTF8StringEncoding) println("Failure: /(failureReason) /(string)") } }
更新 Webservice
func load<A>(resource: Resource<A>, failure: (Reason, NSData?)->(), completion:(A?)->()) { ...... }