Swfit笔记 -如何在Swift中创建通用的网络API(上)

这是我参与更文挑战的第28天,活动详情查看: 更文挑战

如何在Swift中创建通用的网络API

App实现网络获取数据时,通常需要支持许多不同的服务器站点,虽然这些站点会返回不同类型的结构数据,但调用它们的底层网络逻辑是非常相似的。

Modeling shared structures

当使用某些Web API时,尤其是那些遵循类似REST设计的API时,收到嵌套数据结构,同时包含通用密钥的数据JSON,这是非常常见的。例如,以下JSON使用result作为顶级密钥:

{
    "result": {
        "id": "D4F28578-51BD-40F4-A8BD-387668E06EF8",
        "name": "John Sundell",
        "twitterHandle": "johnsundell",
        "gitHubUsername": "johnsundell"
    }
}
复制代码

那在客户端怎么最优雅的处理这种数据逻辑呢?

一种方式是提取成一个专用的响应类型,然后我们可以直接对数据进行解码。比如,上面的JSON代表一个User模型对象

struct User: Identifiable, Codable {
    let id: UUID
    var name: String
    var twitterHandle: String
    var gitHubUsername: String
}

extension User {
    struct NetworkResponse: Codable {
        var result: User
    }
}
复制代码

有了上面的对象,我们就可以这样对返回的数据进行解码

struct UserLoader {
    var urlSession = URLSession.shared

    func loadUser(withID id: User.ID) -> AnyPublisher<User, Error> {
        urlSession.dataTaskPublisher(for: resolveURL(forID: id))
            .map(\.data)
            .decode(type: User.NetworkResponse.self, decoder: JSONDecoder())
            .map(\.result)
            .eraseToAnyPublisher()
    }
}
复制代码

虽然上面的代码肯定没有问题,但总是得为返回的对象创建专用的NetworkResponse包装器;这会导致非常多的重复代码。所以让在看看是否能想出一个更通用、可重用的抽象逻辑。

因为每个网络响应都遵循相同的结构,可以先创建一个通用的网络NetworkResponse类型开始,使用它来加载任何网络数据模型

struct NetworkResponse<Wrapped: Decodable>: Decodable {
    var result: Wrapped
}
复制代码

我们现在可以不需要为每种请求创建和维护单独的包装器类型,可以这样做

struct UserLoader {
    var urlSession = URLSession.shared

    func loadUser(withID id: User.ID) -> AnyPublisher<User, Error> {
        urlSession.dataTaskPublisher(for: resolveURL(forID: id))
            .map(\.data)
            .decode(type: NetworkResponse<User>.self, decoder: JSONDecoder())
            .map(\.result)
            .eraseToAnyPublisher()
    }
}
复制代码

使用泛型类型对网络请求进行改造,为我们创建了便利性API

除了上述loaduser方法的返回类型之外,它的内部逻辑实际上没有任何User的东西——事实上,当加载我们应用的任何模型时,我们可能会编写或多或少完全相同的代码——所以让我们将该逻辑提取到共享抽象中:

extension URLSession {
    func publisher<T: Decodable>(
        for url: URL,
        responseType: T.Type = T.self,
        decoder: JSONDecoder = .init()
    ) -> AnyPublisher<T, Error> {
        dataTaskPublisher(for: url)
            .map(\.data)
            .decode(type: NetworkResponse<T>.self, decoder: decoder)
            .map(\.result)
            .eraseToAnyPublisher()
    }
}
复制代码

回到前面的UserLoader类,现在只需要一行代码

struct UserLoader {
    var urlSession = URLSession.shared

    func loadUser(withID id: User.ID) -> AnyPublisher<User, Error> {
        urlSession.publisher(for: resolveURL(forID: id))
    }
}
复制代码

同时,我们可以创建一个通用的模型加载器,模型可以为URL提供指定ID,这样可以加载任何模型对象

struct ModelLoader<Model: Identifiable & Decodable> {
    var urlSession = URLSession.shared
    var urlResolver: (Model.ID) -> URL

    func loadModel(withID id: Model.ID) -> AnyPublisher<Model, Error> {
        urlSession.publisher(for: urlResolver(id))
    }
}
复制代码

本文原文来自swiftbysundell,非常喜欢他们的写文章,通俗易懂,而且非常严谨,虽然我的英语很烂,但只要你认真读下来也不是很难。swiftbysundell里面有许多关与Swift,SwiftUI开发的文章,每天学习一点,进步就多一点点。

今天算是学习swift第20天吧,坚持不容易,哈哈,给自己点个赞!

明天继续

  1. 封装多端点网络请求逻辑

  2. 静态工厂方法应用

参考

www.swiftbysundell.com/articles/cr…

猜你喜欢

转载自juejin.im/post/6979172586273898509