RestingKit
介绍
RestingKit 是一个 Swift 编写的库,它对Alamofire和PromiseKit进行了高级包装,允许开发者专注于重要事情,而不是编写 REST API 的样板代码。
功能
- 可配置的 HTTP 客户端(目前仅提供 Alamofire,但您可以编写自己的客户端!)
- 由 GRMustache.swift 提供的路径变量扩展功能
- 拦截(并修改)所有请求和响应
要求
-
iOS 10.0+
-
Swift 5.0+
安装
CocoaPods
CocoaPods是Cocoa项目的依赖管理工具。有关使用和安装说明,请访问他们的网站。要使用CocoaPods将RestingKit集成到您的Xcode项目中,请在您的Podfile
中指定它。
Carthage
Carthage是一个去中心化的依赖管理工具,它可以构建您的依赖并提供二进制框架。要使用Carthage将RestingKit集成到您的Xcode项目中,请在您的Cartfile
中指定它。
github "moray95/RestingKit"
示例项目
示例项目包含在仓库中。要运行它,首先执行carthage update
,然后打开RestingKit.project
。如果要在示例应用程序中测试文件上传,请进入image_server
目录并运行php -S 0.0.0.0:9000 -c .
,这将为您的上传启动一个虚拟服务器。上传将存储在uploads
目录中。
用法
基本示例
- 创建一个
RestingClient
import RestingKit
let restingClient = RestingClient(baseUrl: "https://jsonplaceholder.typicode.com")
RestingClient
是 RestingKit 的核心类,负责执行请求的重负载。它配置为使用单个基本 URL,因此如果您需要访问多个 API,则必须创建多个客户端。
-
定义您的模型和端点
struct PostCreateModel: Codable { let userId: Int let title: String let body: String init(userId: Int, title: String, body: String) { self.userId = userId self.title = title self.body = body } } struct PostModel: Codable { let id: Int let userId: Int let title: String let body: String } let createPostEndpoint = Endpoint<PostCreateModel, PostModel>(.post, "/posts", encoding: .json)
Endpoint
由请求和响应的模型定义,路径(相对于RestingClient
的baseUrl
),使用的 HTTP 方法以及编码。如果请求不期望任何内容或返回任何内容,您可以使用特殊类Nothing
。理想情况下,我们会使用Void
,但这不可能使其成为Encodable
或Decodable
。 -
创建请求并实际调用
let postCreateModel = PostCreateModel(userId: 1, title: "Hello world", body: "Some awesome message") let request = RestingRequest(endpoint createPostEndpoint, body: postCreateModel) restingClient.perform(request).done { response in print("Headers: \(response.headers)") let post = response.body print("Created post with id: \(post.id)") }.catch { error in print("An error occurred: \(error)") }
当服务器响应 HTTP 状态 > 299 时,promise 将失败,因此您不需要处理此情况。
就是这样!
处理无内容的响应
如果请求可能提供可能为空的响应,您可以创建一个具有可选响应类型的 Endpoint
。这样,如果响应为空,则返回 nil
。
let createPostEndpoint = Endpoint<PostCreateModel, PostModel?>(.post,
"/posts",
encoding: .json)
let postCreateModel = PostCreateModel(userId: 1,
title: "Hello world",
body: "Some awesome message")
let request = RestingRequest(endpoint createPostEndpoint,
body: postCreateModel)
restingClient.perform(request).done { response in
print("Headers: \(response.headers)")
if let post = response.body {
print("Created post with id: \(post.id)")
} else {
print("Empty body")
}
}.catch { error in
print("An error occurred: \(error)")
}
注意: 要使此功能正常工作,响应必须是真正为空(即内容长度为 0)。一个空的 JSON 对象将产生解码错误。
路径变量
提供的 RestingRequestConverter
允许使用 Mustache.swift 在路径中进行模板化。
let getPostEndpoint = Endpoint<Nothing, PostModel>(.get,
"/posts/{{post_id}}",
encoding: .query)
let request = RestingRequest(endpoint: getPostEndpoint,
body: Nothing(),
pathVariables: ["post_id": 1])
restingClient.perform(request).done { response in
print("Got post: \(response.body)")
}.catch { error in
print("An error occurred: \(error)")
}
多部分表单数据 & 文件上传
通过 RestingKit 执行多部分表单数据请求是可能的。只有一个东西,设置 Endpoint
的编码为 .multipartFormData
。
let multipartEndpoint = Endpoint<MyModel, Nothing>(.post,
"/some_resource",
encoding: .multipartFormData)
现在,每个使用此端点的请求都会编码为 multipart/form-data
。上传文件也非常简单。您可以在模型中使用提供的 MultipartFile
类,神奇的是,文件将自动上传。
class ImageUploadModel: Encodable {
let file: MultipartFile
init(imageURL: URL) {
self.file = MultipartFile(url: imageURL)
}
}
let request = RestingRequest(endpoint: multipartEndpoint,
body: ImageUploadModel(url: imageUrl))
restingClient.upload(request).promise.done { _ in
print("Success!")
}.catch {
print("Error: \($0)")
}
注意: 在处理文件和大量数据时,应使用 RestingClient
上的 upload
方法而不是 perform
。`perform` 会将整个请求数据加载到内存中,而 `upload` 将将其存储到临时文件并流式传输,而无需加载到内存中。
编码由 MultipartFormDataEncoder
处理,它提供了一个类似于 JSONEncoder
的接口和配置选项。您可以为 RestingRequestConverter
定制使用的 MultipartFormDataEncoder
。
let formDataEncoder = MultipartFormDataEncoder()
formDataEncoder.keyEncodingStrategy = .convertToSnakeCase
formDataEncoder.dateEncodingStrategy = .secondsSince1970
formDataEncoder.dataEncodingStrategy = .raw
let configuration = RestingRequestConverter.Configuration(multipartFormDataEncoder: formDataEncoder)
let converter = RestingRequestConverter(configuration: configuration)
进度处理程序
RestingClient
的 upload
方法返回一个 ProgressablePromise
,它类似于经典承诺,但同时也接受进度处理程序。
restingClient.upload(request).progress { progress in
print("Upload \(progress.fractionCompleted * 100)% completed")
}.done { response in
print("Uploaded completed with response: \(response)")
}.catch { error in
print("An error occurred")
}
拦截器
拦截器允许拦截任何请求和响应,在请求发送或响应处理之前修改它。拦截器的一些基本用法包括
- 记录请求和响应
- 注入头部信息
- 重试失败的请求
要使用拦截器,您需要实现 RestingInterceptor
协议,并将您的拦截器提供给您的 RestingClient
。
class LogInterceptor: RestingInterceptor {
func intercept(request: HTTPRequest, execution: Execution)
-> ProgressablePromise<HTTPDataResponse> {
print("sending request \(request)")
return execution(request).get { response in
print("got response \(response)")
}
}
}
class DeviceIdInjector: RestingInterceptor {
func intercept(request: HTTPRequest, execution: Execution) -> ProgressablePromise<HTTPDataResponse> {
var urlRequest = request.urlRequest
urlRequest.setValue(UIDevice.current.identifierForVendor?.uuidString,
forHTTPHeaderField: "device-id")
let request = BasicHTTPRequest(urlRequest: urlRequest, fileUrl: request.fileUrl)
return execution(request)
}
}
let restingClient = RestingClient(baseUrl: "https://jsonplaceholder.typicode.com",
requestConverter: requestConverter,
interceptors: [DeviceIdInjector(), LogInterceptor()])
RestingClient
将按照提供的顺序将请求传递给拦截器,而响应则按相反的顺序传递。因此,将 LogInterceptor
放在数组末尾很重要(否则,它将无法记录由 DeviceIdInjector
添加的 device-id
头部信息)。
RestingKit 提供了一个用于记录请求和响应的拦截器:RequestResponseLoggingInterceptor
。
重要:每个拦截器都必须调用 execution
参数,因为它将运行下一个拦截器,最终是请求。除非您不希望运行附加的拦截器或发送请求。
使用自定义 HTTPClient
HTTPClient
是执行请求的类。它们接受一个 HTTPRequest
并返回一个 (Progressable)Promise<HTTPDataResponse>
而不执行任何操作。AlamofireClient
是提供的实现,使用 Alamofire 执行请求,是 RestingClient
使用的默认客户端。您可以将 RestingClient
配置为使用您的实现
class MyHTTPClient: HTTPClient {
public func perform(urlRequest: URLRequest) -> Promise<HTTPDataResponse> {
// Handle classic request
}
func upload(request: HTTPRequest) -> ProgressablePromise<HTTPDataResponse> {
// Handle uplaod request, with a progress handler
}
}
let restingClient = RestingClient(baseUrl: "https://jsonplaceholder.typicode.com",
httpClient: MyHTTPClient())
始终开启的标头和路径变量
有时,您需要在所有请求中包含相同的标头或路径变量。RestingKit已为您解决此问题!可以通过配置RestingRequestConverter
来添加标头和在所有请求中包含路径变量。
例如,可以将DeviceIdInjector
拦截器按以下方式重写
let headerProvider = RestingHeaderProvider(providers: [ "device-id": { UIDevice.current.identifierForVendor?.uuidString } ])
// Or
headerProvider.addHeader(key: "device-id") { UIDevice.current.identifierForVendor?.uuidString }
// Or
if let deviceId = UIDevice.current.identifierForVendor?.uuidString {
headerProvider.addHeader(key: "device-id", value: deviceId)
}
let configuration = RestingRequestConverter.Configuration(headerProvider: headerProvider)
let requestConverter = RestingRequestConverter(configuration: configuration)
现在,所有请求都会包含device-id
。
以同样的方式,您也可以提供路径变量
let pathVariableProvider = RestingPathVariableProvider(providers: [ "userId": { AuthManager.user?.id } ])
// Or
pathVariableProvider.addVariable(key: "user-id") { AuthManager.user?.id }
// Or
if let userId = AuthManager.user?.id {
pathVariableProvider.addVariable(key: "user-id", value: userId)
}
let configuration = RestingRequestConverter.Configuration(pathVariableProvider: pathVariableProvider)
let requestConverter = RestingRequestConverter(configuration: configuration)
现在,您可以直接使用路径/users/{{userId}}/info
而无需向RestingRequest
提供userId
。
当使用标头和路径变量提供程序时,添加到个别请求的值将覆盖由提供程序提供的值。
开发中的内容
由于RestingKit仍然处于新开发和实现中,因此某些功能仍需实施。
- 文件下载
- 您可能请求的任何其他功能!
此外,在项目完全成熟之前,可能还会出现一些破坏API的更改。
贡献
如果您需要帮助入门或请求功能,只需打开一个问题。欢迎使用拉取请求,为修复bug和新功能做出贡献。
作者
莫雷·巴拉·布尔克·凯勒罗格鲁
许可
RestingKit受MIT许可证的许可。有关更多信息,请参阅LICENSE文件。