在 Swift 中使用泛型和结构体来构建网络层

objc.io 推出一个新的谈话节目,一周一期,每期的主题固定,用 20 多分钟的时间去详述一个主题,很有意思,近期做个记录,内容比较简单就不翻译了,主要记录一下背后的思想。

一般网络层所做的工作无非就是发起请求,得到 NSData,然后进行 json 解析,接着映射成对应的 Model,最后传递给回调函数。比如我想得到一个填满 Episodes 类型的数组,但网络只能给你返回一个 id 类型,如果用传统的 OC 来写,这背后就涉及到复杂的类型转换,上面提到的每一步都可能出错。不过在 Swift 中我们有更好的办法。

1. 架构

创建了一个带泛型参数的 Resource<A> 结构体作为网络请求和 Model 交流的桥梁:

Resource<A> 作为中间层对 Webservice 和 Model 一无所知

2. 各司其职

2.1 Webservice

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()
    }
}

2.2 Resource

提供网络请求需要的一些参数和一个把 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 处理。

2.3 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
    }
}

4. 优势

5. One More Thing

谈话到此结束,让我们发散一下思维,其实还可以为 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?)->()) {  
    ......
}

-EOF-

如果感觉此文对你有帮助,请随意打赏支持作者 😘

chengway

认清生活真相之后依然热爱它!

Subscribe to Talk is cheap, Show me the world!

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!