Legatus(英语化为Legate)是罗马军队中高级别的罗马军官,相当于现代的高级将官。最初用于委托权力,该术语在奥古斯都统治下正式确定为军团的指挥官。 Legatus也是罗马共和国大使的术语,由参议院任命执行前往外国的任务(legatio),也用于从其他国家来到罗马的大使。
Legatus 的基本思想是,我们需要一个网络抽象层,能够充分地封装直接调用 Alamofire 的行为。
此外,如果有一个与 SwiftUI 开箱即用的网络层 📦,岂不是很棒?🧐
幸运的是,Legatus 使用 Combine
框架实现,并具有一些奇特的方法,允许你将响应模型 assign(to:on:)
到 @Published
属性。 太棒了!🤩
APIClient
不存储和配置请求,每个请求都封装在单独的实体中)。Legatus 的灵感来自 Moya。
我认为它已准备好用于生产环境。
欢迎任何贡献(拉取请求、问题、建议)!😃
你可以使用 Xcode SPM GUI:File -> Swift Packages -> Add Package Dependency -> 选择 "Up to Next Major Version 2.0.0"。
或者将以下内容添加到你的 Package.swift
文件中
.package(url: "https://github.com/artemkalinovsky/Legatus.git", .upToNextMajor(from: Version("2.0.0")))
然后在你希望使用 Legatus 的 Target 中指定 "Legatus"
作为依赖项。 这是一个示例 PackageDescription
// swift-tools-version:5.6
import PackageDescription
let package = Package(
name: "MyPackage",
products: [
.library(
name: "MyPackage",
targets: ["MyPackage"]),
],
dependencies: [
.package(url: "https://github.com/artemkalinovsky/Legatus.git", .upToNextMajor(from: Version("2.0.0")))
],
targets: [
.target(
name: "MyPackage",
dependencies: ["Legatus"])
]
)
假设我们要从 JSON 获取用户列表,并且响应如下所示
{
"results":[
{
"name":{
"first":"brad",
"last":"gibson"
},
"email":"brad.gibson@example.com"
}
]
}
APIClient
let apiClient = APIClient(baseURL: URL(string: "https://webservice.com/api/")!)
import Foundation
import Legatus
final class User: Decodable {
let firstName: String?
let lastName: String?
let email: String?
enum CodingKeys: String, CodingKey {
case name
case email
}
enum NameKeys: String, CodingKey {
case firstName = "first"
case lastName = "last"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
email = try values.decodeIfPresent(String.self, forKey: .email)
let name = try values.nestedContainer(keyedBy: NameKeys.self, forKey: .name)
firstName = try name.decodeIfPresent(String.self, forKey: .firstName)
lastName = try name.decodeIfPresent(String.self, forKey: .lastName)
}
}
import Foundation
import Legatus
final class UsersApiRequest: DeserializeableRequest {
var path: String {
"users"
}
var deserializer: ResponseDeserializer<[User]> {
JSONDeserializer<User>.collectionDeserializer(keyPath: "results")
}
}
apiClient.executeRequest(request: UsersApiRequest()) { result in }
瞧!🧑🎨
要将响应直接反序列化到 CoreData NSManagedObject
,请首先调用指定的初始化器
import Foundation
import CoreData
import Legatus
@objc(CoreDataObject)
public class CoreDataObject: NSManagedObject, Decodable {
required convenience public init(from decoder: Decoder) throws {
self.init(context: NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType))
//TODO: implement decoding
}
}
要将响应直接反序列化到 Realm Object
子类
import Foundation
import RealmSwift
import Legatus
final class RealmObject: Object, Decodable {
@objc dynamic var name = ""
required init() {
super.init()
}
convenience init(from decoder: Decoder) throws {
self.init()
//TODO: implement decoding
}
}
同样的功能也适用于 XMLDeserializer
。
如果要重试先前失败的请求,只需提供所需的重试次数
apiClient.executeRequest(request: UsersApiRequest(), retries: 3) { result in }
要取消某个请求,你必须存储它的取消令牌并调用 cancel()
方法。
let cancelationToken = apiClient.executeRequest(request: UsersApiRequest()) { result in }
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) {
cancelationToken.cancel()
}
此外,你可以取消所有活动请求
apiClient.cancelAllRequests()
在使用 SwiftUI 时,其中大多数 UI 更新都基于底层的 Combine 机制,因此获取 Publisher
作为请求结果以进行未来的转换和赋值非常方便
@Published var users = [User]()
var subscriptions = Set<AnyCancellable>()
apiClient
.responsePublisher(request: UsersApiRequest())
.catch { _ in return Just([User]())}
.assign(to: \.users, on: self)
.store(in: &subscriptions)
do {
let usersResponse = try await apiClient.executeRequest(request: UsersApiRequest())
} catch {
// handle error
}
Legatus 在 MIT 许可证下发布。 有关更多信息,请参见 LICENCE。