Skip to content

Latest commit

 

History

History
92 lines (68 loc) · 3.87 KB

9.3 使用泛型进行代码设计Designing with Generics.md

File metadata and controls

92 lines (68 loc) · 3.87 KB

第九章:泛型 Generics

9.3 使用泛型进行代码设计 Designing with Generics

本小节就比较实用了,对网络请求以及数据转模型的实际场景使用了泛型的二次封装。 这样的编程思想还是很酷的,我们也可以在自己的项目中推广一下

普通的网络请求已经数据转模型的代码
    func loadUsers(callback: ([User]?) -> ()) {
        ///获取数据
        let usersURL = webserviceURL.appendingPathComponent("/users") let data = try? Data(contentsOf: usersURL)
        ///数据转模型
        let json = data.flatMap {
        try? JSONSerialization.jsonObject(with: $0, options: []) }
        let users = (json as? [Any]).flatMap { jsonObject in jsonObject.flatMap(User.init)
        }
        ///回调
        callback(users) 
    }

需要注意的是

1️⃣网络请求可能失败

2️⃣JSON解析可能失败

3️⃣数据转模型可能失败 这三种情况都可能会返回nil, 可以通过flapMap来排除nil的情况

提取共通功能

上面的Demo是对[User]模型的网络请求, 如果我们需要请求订单量[Bill]的网络请求,又需要写一个新的方法重复写很多代码。 这样不可取 我们可以把函数中的User提取出来, URLString作为参数传入来封装一个泛型方法 loadResource, 声明为A的泛型

    func loadResource<A>(at path: String, parse: (Any) -> A?, callback: (A?) -> ())
    {
        let resourceURL = webserviceURL.appendingPathComponent(path) let data = try? Data(contentsOf: resourceURL)
        let json = data.flatMap {
        try? JSONSerialization.jsonObject(with: $0, options: []) }
        callback(json.flatMap(parse)) 
    }

封装完毕之后我们对请求[User]的网络请求就可以这样写了

    func loadUsers(callback: ([User]?) -> ()) {
        loadResource(at: "/users", parse: jsonArray(User.init), callback: callback)
    }

上面用了一个辅助函数,jsonArray 主要作用是: 它首先尝试将一个 Any 转换为一个 Any 的数组,接着 对每个元素用提供的解析函数进行解析,如果期间任何一步发生了错误,则返回 nil:

    func jsonArray<A>(_ transform: @escaping (Any) -> A?) -> (Any) -> [A]?  
    {
         return { array in
            guard let array = array as? [Any] else { return nil
        }
        return array.flatMap(transform) }
    }

二次优化--创建泛型数据类型

上面的loadResource函数中 path 和 parse 耦合非常紧 , 一旦path改变了 parse也会一起改变。 我们可以封装成一个结构体,来描述要加载的资源

struct Resource<A> { 
    let path: String
    let parse: (Any) -> A?
}

接下来 我们可以给 我们已经封装好的结构体 Resource 结构体来定义一个extension 让Resource 直接调用网络请求数据转模型的方法

extension Resource {
    func loadSynchronously(callback: (A?) -> ()) {
        let resourceURL = webserviceURL.appendingPathComponent(path)
        let data = try? Data(contentsOf: resourceURL)
        let json = data.flatMap {
        try? JSONSerialization.jsonObject(with: $0, options: []) }
        callback(json.flatMap(parse)) }
}

最终我们封装好的战果😎~

///网络请求一个用户模型数组就变的如此方便。
let usersResource: Resource<[User]> = Resource(path: "/users", parse:jsonArray(User.init))
userResourse.loadSynchronously = { userModels in  
    ///我取到了我想要的 userModels 模型数组啦~~
}
感悟

这一小节是真的有用,对于我们实际开发及代码风格有很大的启发。 下次代码优化的时候或许可以试试这样的封装思想。