Yggdrasil 0.1.7

Yggdrasil 0.1.7

Thomas Sempf 维护。



 
依赖
Alamofire~> 4.7
Taskig~> 0.2
 

Yggdrasil 0.1.7

Yggdrasil

CI Status Version License Platform Swift

Yggdrasil 是一个网络库,允许创建和执行基于 async/await 的网络请求。重点在于简单易用,以减少过多的代码开销。Yggdrasil 基于 protocol,并提供一些结构体和类以便于使用。

有关 async/await 更多的信息,请参阅Taskig,这是底层的 async/await 库。

内部,Yggdrasil 使用TaskigAlamofire

快速开始

启动一个简单的文件下载任务。

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的任务执行。任务可以通过RequestTypesEndpointTypes或基于字符串的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'

作者

[email protected]

许可证

Yggdrasil 可在 MIT 许可下使用。有关更多信息,请参阅 LICENSE 文件。