RestingKit 0.0.8

RestingKit 0.0.8

Moray Baruh 维护。



 
依赖项
Alamofire~> 5.0
GRMustache.swift~> 4.0
PromiseKit~> 6.8
 

  • 作者
  • Moray Baruh

RestingKit

CI Status Version Carthage Compatible License Platform

介绍

RestingKit 是一个 Swift 编写的库,它对AlamofirePromiseKit进行了高级包装,允许开发者专注于重要事情,而不是编写 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目录中。

用法

基本示例

  1. 创建一个RestingClient
import RestingKit

let restingClient = RestingClient(baseUrl: "https://jsonplaceholder.typicode.com")

RestingClient 是 RestingKit 的核心类,负责执行请求的重负载。它配置为使用单个基本 URL,因此如果您需要访问多个 API,则必须创建多个客户端。

  1. 定义您的模型和端点

    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 由请求和响应的模型定义,路径(相对于 RestingClientbaseUrl),使用的 HTTP 方法以及编码。如果请求不期望任何内容或返回任何内容,您可以使用特殊类 Nothing。理想情况下,我们会使用 Void,但这不可能使其成为 EncodableDecodable

  2. 创建请求并实际调用

    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)

进度处理程序

RestingClientupload 方法返回一个 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文件。