Yggdrasil 是一个网络库,允许创建和执行基于 async/await 的网络请求。重点在于简单易用,以减少过多的代码开销。Yggdrasil 基于 protocol,并提供一些结构体和类以便于使用。
有关 async/await 更多的信息,请参阅Taskig,这是底层的 async/await 库。
内部,Yggdrasil 使用Taskig 和 Alamofire。
快速开始
启动一个简单的文件下载任务。
let imageDownloadTask = DownloadTask(url: "https://picsum.photos/1024/1024")
let fileURL = try imageDownloadTask.await()
这定义了一个下载任务,它将返回下载数据的主机名。调用 .await()
启动指定 URL 的下载请求。请求在后台队列上执行,当前线程在获取结果或发生错误之前暂停。Yggdrasil 使用基于 do/catch 的错误处理,可以将请求代码与错误处理代码分开。小心不要在主线程上以 .await()
的方式启动任务,因为这会阻塞 UI。
或者将相同内容作为数据任务下载
// Data task which returns the downloaded data as Swift data structure
// The response type needs to be defined as type parameter
let imageData = try DataTask<Data>(url: "https://picsum.photos/1024/1024").await
启动上传任务同样简单直接
// Upload task which does a POST request to the given URL
// The response is a JSON dictionary
let uploadTask = UploadTask<JSONDictionary>(url: "https://httpbin.org/post",
dataToUpload: .data(imageData))
let jsonUpload = try uploadTask.await()
架构
Yggdrasil 基于一组协议,这些协议定义了 API 端点、请求类型和响应值的要求。根据这些协议,Yggdrasil 提供便利的结构体/类,以简化使用并减小代码尺寸。让我们看看底层的协议。
端点
EndpointType
协议定义了 API 端点的基础需求:基本 URL、路径、方法和参数。您可以通过枚举等帮助定义自己的 API 端点。
// An enum adopting the EndpointType protocol defining two API endpoints
enum BaconIpsumEndpoints: EndpointType {
case meatAndFiller
case allMeat
var baseUrl: String { return "https://baconipsum.com" }
var path: String {
switch self {
case .meatAndFiller:
return "/api"
case .allMeat:
return "/api/"
}
}
var parameters: [String : Any] {
switch self {
case .meatAndFiller:
return ["type": "meat-and-filler"]
case .allMeat:
return ["type": "all-meat", "paras" : "2", "start-with-lorem": "1"]
}
}
}
便利的结构体 Endpoint
可以用来定义端点,无需定义自己的枚举或结构体。
// Defines an endpoint with parameters
let endpoint = NetworkEndpoint(baseUrl: "https://baconipsum.com",
path: "/api",
parameters: ["type": "meat-and-filler"])
请求
RequestType
协议定义了包含所有可能参数的实际网络请求,这包括端点、标头、体、重试次数、忽略本地缓存、响应验证和预条件。
// Network request using the defined BaconIpsumEndpoints
struct LoremRequest: RequestType {
let endpoint: EndpointType = BaconIpsumEndpoints.meatAndFiller
let retryCount = 3
let ignoreLocalCache = true
}
此请求定义将 retryCount
参数设置为 3,这意味着在放弃并返回错误之前,将在初始请求之后尝试获取请求 3 次。此外,将 ignoreLocalCache
参数设置为 true,这将忽略请求期间的本地缓存数据。
便利的结构体 Request
允许轻松创建请求,无需专用结构或枚举。
let request = Request(url: "https://picsum.photos/1024",
ignoreLocalCache: true,
retryCount: 2)
预条件 & 响应验证
可以在请求中添加预条件和响应验证检查,以确保在请求开始或结束时满足特定条件。如果所有预条件下都满足,则才会启动请求。同样,如果满足所有响应验证,则请求才成功完成。每个预条件和响应验证检查都必须返回一个验证结果,即 .success()
或 .failure(YourErrorHere)
。
// Network request with preconditions and response validations
struct LoremRequest: RequestType {
let endpoint: EndpointType = BaconIpsumEndpoints.meatAndFiller
var preconditions: [PreconditionValidation] = []
var responseValidations: [ResponseValidation] = []
}
var loremRequest = LoremRequest()
// Adding a precondition check
loremRequest.preconditions.append({ () -> ValidationResult in
guard self.isUserSignedIn() else {
return .failure(MyErrors.noActiveUser)
}
return .success
})
// Adding a response validation check
loremRequest.responseValidations.append({ (request, response, data) -> ValidationResult in
guard response.statusCode < 300 else {
return .failure(MyErrors.wrongStatusCode)
}
return .success
})
多部分请求
MultipartFormDataRequestType
协议和相关结构体 MultipartFormDataRequest
可用于创建上传文件的多部分请求,例如上传图像或其他二进制数据。它继承自 RequestType
并增加了数据、数据名和 MIME 类型属性。
let multipartEndpoint = Endpoint(baseUrl: "https://httpbin.org",
path: "/post",
method: .post)
let multipartRequest = MultipartFormDataRequest(endpoint: multipartEndpoint,
data: imageData,
mimeType: "jpeg",
dataName: "MyImage")
执行任务
请求由基于async/await的任务执行。任务可以通过RequestTypes
、EndpointTypes
或基于字符串的URL
来初始化。然后通过调用.await()
或.async()
来执行,它们将在后台线程上执行。.await()
将暂停当前线程,直到任务完成。async()
期望一个完成处理器,该处理器应该处理请求的结果。
可解析 & 返回类型
每个任务都有一个类型参数定义了期望的返回类型,例如JSON字典或特定的数据结构。这些返回类型必须遵守Parsable
协议。例外情况是DownloadTask,它具有预定义的URL
返回类型。支持采用Decodable
协议的类型。
// MARK: - Define decodable struct for httpbin.org/uuid
// httpbin.org/uuid response
// { "uuid": "0a4f1b83-8781-4258-8d72-635edbfa79b5" }
struct HttpBinUUID: Decodable {
let uuid: String
}
// Make parsable
extension HttpBinUUID: Parsable {}
其他类型必须采用Parsable
协议。
// Add support for parsable to UIImage
// This needs a wrapper class which is marked as final to conform to Parsable
final class Image: UIImage, Parsable {
static func parseData(_ data: Data) throws -> Image {
guard let image = Image(data: data) else {
throw MyErrors.ImageConversionFailed
}
return image
}
}
DataTask
数据分析任务获取给定请求的数据,并使用Parsable
协议将其转换为给定的类型参数指定的数据类型。
let imageEndPoint = Endpoint(baseUrl: "https://picsum.photos", path: "/2048/2048")
let image = try DataTask<Data>(endpoint: imageEndPoint).await()
DownloadTask
下载任务获取给定请求的数据并将其保存为文件。
let imageDownloadTask = DownloadTask(url: "https://picsum.photos/1024/1024")
let fileURL = try imageDownloadTask.await()
还可以指定自定义的下载目标。
// Download task with custom file URL
let downloadDestinationURL = FileManager.default
.temporaryDirectory
.appendingPathComponent("MyImage")
.appendingPathExtension("jpeg")
try DownloadTask(url: "https://picsum.photos/1024/1024/?random",
downloadDestination: downloadDestinationURL).await()
self.imageView.setImageWith(contentsOfFile: downloadDestinationURL)
UploadTask
上传任务允许向指定的URL
或请求
发布文件URL或数据结构。
// Data upload with JSON response
let data = "Foobar".data(using: .utf8)!
let uploadTask = UploadTask<JSONDictionary>(url: "https://httpbin.org/post", dataToUpload: .data(data))
let jsonResult = try uploadTask.await()
MultipartFormDataUploadTask
使用此任务执行多部分文件上传请求。
let multipartRequest = MultipartFormDataRequest(endpoint: multipartEndpoint,
data: imageData,
mimeType: "jpeg",
dataName: "MyImage")
let multipartUploadTask = MultipartFormDataUploadTask<Data>(request: multipartRequest)
let resultData = try multipartUploadTask.await()
序列 & 字典支持
可以通过 .awaitAll()
执行任务数组并收集所有结果。如果某个任务失败,将会抛出错误。
let iconEndPoint = Endpoint(baseUrl: "https://picsum.photos",
path: "/256/256/?random")
let iconImages = try (0..<10)
.map({ _ in DataTask<Image>(endpoint: iconEndPoint) })
.awaitAll()
为了逐个处理错误,可以使用 .awaitAllResults()
执行一系列任务,然后可以处理或过滤它们,例如通过紧凑映射(compactMap)。要处理任务的结果或错误,请在从 .awaitAllResults()
调用中检索到的 TaskResult
结构上调用 unwrap()
。
let iconImages = try (0..<10)
.map({ _ in DataTask<Image>(endpoint: iconEndPoint) })
.awaitAllResults()
.compactMap({ (resultImage) -> Image? in
try? resultImage.unpack()
})
支持进度报告
所有任务类型都支持 ProgressReporting
协议。《.progress》 属性提供对底层进度对象的访问,这可以将它传递给 UIProgressView
。
let downloadTask = DownloadTask(url: "https://picsum.photos/1024/1024/?random")
// Track progress from the download task
DispatchQueue.main.sync {
self.progressView.observedProgress = downloadTask.progress
}
let imageURL = try downloadTask.await()
示例项目
要运行示例项目,请克隆仓库,然后首先从 Example 目录运行 pod install
。
需求
安装
可以通过CocoaPods 使用 Yggdrasil。要安装它,只需将以下行添加到 Podfile。
pod 'Yggdrasil'
作者
许可证
Yggdrasil 可在 MIT 许可下使用。有关更多信息,请参阅 LICENSE 文件。