Fetch
Fetch 是基于 Alamofire 的资源型网络抽象
功能
- 用类型化的资源定义API
- 使用 Decodable 解码 HTTP 响应体
- 使用 Encodable 编码 HTTP 请求体
- 根键:解码多级包装容器
- 日志记录
- 存根
- 简单的响应或错误
- 从数组中随机存根
- 循环遍历存根数组
- 缓存
- 多种缓存策略
- 内存缓存
- 磁盘缓存
基本用法
首先使用 Config
设置 APIClient
。这是一行代码。在 application(_:didFinishLaunchingWithOptions:)
函数中调用它。
APIClient.shared.setup(with: Config(baseURL: URL(string: "https://api.github.com")!))
让我们创建一个名为 Organization
的结构体,包含一些属性。模型将从网络响应中解析。
struct Organization: Decodable, Equatable {
let id: Int
let name: String
let location: String
}
Resource
包含创建网络请求并解析响应所需的所有信息。
let resource = Resource<Organization>(
method: .get,
path: "/orgs/allaboutapps")
发送请求并将响应解析为类型化的模型 Organisation
resource.request { (result) in
switch result {
case .success(let networkResponse):
print("Status code:", networkResponse.urlResponse.statusCode)
print("Model:", networkResponse.model)
case .failure(let apiError):
print("Error:", apiError)
}
}
高级用法
内容解析
默认情况下,配置使用标准库中提供的JSONDecoder和JSONEncoder,但不限于它。这两种类型都已扩展以符合ResourceDecoderProtocol和ResourceEncoderProtocol,允许您定义自己的自定义解码器/编码器。资源提供了使用配置中定义的解码器和编码器进行编码和解码的闭包。如果您想为资源实现不同的行为,您可以在创建资源时提供一个闭包。
有效载荷解包
有时,内容被包装在信封中,这使得解析很困难。在这种情况下,您可以定义所谓的“根键”。根键定义了您想要解析的信封中的内容路径。这意味着只有使用根键定义的内容将被解析。
示例
这是一个应该被解析的响应。
{
"data": {
"people": [
{
"name": "Alex"
},
{
"name": "Jeff"
},
{
"name": "Tom"
},
{
"name": "Xavier"
}
]
}
}
我们只想得到人员数组,这是一个Person数组。我们不需要定义一个结构来模拟层次结构,而是在资源上定义“根键”,以只获取数组。
struct Person: Decodable {
let name: String
}
let resource = Resource<[Person]>(
path: "/people",
rootKeys: ["data", "people"]
)
resource.request { result in
...
}
模拟
Fetch提供了一套灵活的模拟方法。
模拟一个成功的带有JSON响应的网络请求
let stub = StubResponse(statusCode: 200, fileName: "success.json", delay: 2.0)
let resource = Resource<Person>(
path: "/test",
shouldStub: true,
stub: stub)
上述模拟将返回200状态码,从您的应用程序包中加载的从成功的JSON文件的内容,并将被延迟两秒。
模拟未授权错误
let stub = StubResponse(statusCode: 401, fileName: "unauthorized.json", delay: 2.0)
let resource = Resource<Person>(
path: "/unauthorized",
shouldStub: true,
stub: stub)
模拟不仅限于JSON,您还可以提供原始数据或提供一个符合Encodable协议的实例。
使用Encodable进行模拟
struct Person: Encodable {
let name: String
let age: Int
}
let peter = Person(name: "Peter", age: 18)
let stub = StubResponse(statusCode: 200, encodable: peter, delay: 2.0)
let resource = Resource<Person>(
path: "/peter",
shouldStub: true,
stub: stub)
交替模拟
let successStub = StubResponse(statusCode: 200, fileName: "success.json", delay: 0.0)
let failureStub = StubResponse(statusCode: 404, fileName: "notFound.json", delay: 0.0)
let alternatingStub = AlternatingStub(stubs: [successStub, failureStub])
let resource = Resource<Person>(
path: "/peter",
shouldStub: true,
stub: alternatingStub)
每次资源执行时,它将遍历给定的模拟,并始终返回与上次不同的模拟。
随机模拟
RandomStub与AlternatingStub类似,但始终返回数组中的随机模拟。
条件模拟
通过条件模拟可以实现基于特定条件的模拟。
示例
模拟受用户授权保护端点并返回基于您的应用程序授权状态的成功或错误
let conditionalStub = ClosureStub { () -> Stub in
let unauthorizedStub = StubResponse(statusCode: 401, data: Data(), delay: 2)
let okStub = StubResponse(statusCode: 200, data: Data(), delay: 2)
return CredentialsController.shared.currentCredentials == nil ? unauthorizedStub : okStub
}
return Resource(
path: "/auth/secret",
shouldStub: true,
stub: conditionalStub
)
自定义模拟
您可以通过遵守Stub协议来创建自定义模拟。
struct CustomStub: Stub {
...
}
缓存
以下实现了以下缓存类型
设置缓存
let cache = MemoryCache(defaultExpiration: .seconds(3600))
let config = Config(
baseURL: URL(string: "https://example.com")!,
cache: cache,
cachePolicy: .networkOnlyUpdateCache)
let client = APIClient(config: config)
注意:为了使用缓存,您从资源加载的模型必须符合Cacheable协议。
混合缓存
混合缓存允许您结合两个单独的缓存,使用的缓存类型不受限制。
自定义缓存实现
要实现自定义缓存,您必须创建一个符合Cache协议的类/结构体。
class SpecialCache: Cache {
...
}
缓存策略
缓存策略定义资源的加载行为。您可以在资源创建时直接设置策略,或将它作为参数传递给资源的fetch函数。
注意:资源中定义的策略始终优于配置中定义的策略。
从缓存中读取,否则从网络中读取
这将首先尝试从缓存中读取所需数据,如果数据不可用或已过期,则从网络中加载数据。
let resource: Resource<X> = ...
resource.fetch(cachePolicy: .cacheFirstNetworkIfNotFoundOrExpired) { (result, finishedLoading) in
...
}
从网络加载并更新缓存
这将从网络加载数据并更新缓存。完成闭包仅带从网络中获取的值。
let resource: Resource<Person> = ...
resource.fetch(cachePolicy: .networkOnlyUpdateCache) { (result, finishedLoading) in
...
}
从缓存加载数据并始终从网络中加载
这将从缓存中加载数据并从网络中加载数据。您将在异步的完成闭包中获取这两个值。
let resource: Resource<Person> = ...
resource.fetch(cachePolicy: .cacheFirstNetworkAlways) { (result, finishedLoading) in
...
}
有关策略概述,请查看Cache.swift的实现
Carthage
将以下行添加到您的Cartfile中。
github "allaboutapps/Fetch", ~> 1.0
然后运行carthage update
。
Swift 包管理器
使用 Xcode 11+:转到项目 > Swift 包 > +
并输入 [email protected]:allaboutapps/Fetch.git
或手动更新您的Package.swift文件
dependencies: [
.package(url: "[email protected]:allaboutapps/Fetch.git", from: "1.0.9"),
....
],
targets: [
.target(name: "YourApp", dependencies: ["Fetch"]),
]
要求
- iOS 11.0+
- Xcode 10.2+
- Swift 5+