Raccoon 1.3.0

Raccoon 1.3.0

测试已测试
语言语言 SwiftSwift
许可证 MIT
发布上次发布2018年9月
SPM支持 SPM

Manuel García-Estañ 维护。



 
依赖关系
Alamofire~> 4.7
PromiseKit/CorePromise~> 4.4
AlamofireCoreData~> 2.0
 

Raccoon 1.3.0

  • 作者
  • Manuel García-Estañ

Raccoon

Raccoon 是一套协议和工具,整合了 AlamofirePromiseKitCoreData

内部,Raccoon 使用 GrootAlamofireCoreData 将 JSON 序列化为 CoreData 对象,因此您需要熟悉这些库。

Raccoon 围绕

  • Alamofire 4.0.x
  • PromiseKit 4.0.x
  • Groot 2.0.x
  • AlamofireCoreData 1.0.x

有了 Raccoon,您将能够像这样执行 HTTP 请求

let client = Client(context: context)
client.enqueue(userEndpoint)
    .then { (user: User) in
        print(user) // At this point, your user is already inserted in your context
    }
    .catch { error in
        print(error)
}

安装 Raccoon

使用 CocoaPods

将以下行添加到您的 Podfile

pod 'Raccoon'

然后运行 $ pod install

最后,在您需要 Raccoon 的文件中

import Raccoon

如果您还没有安装或集成 CocoaPods 到您的项目中,您可以在 这里 学习如何这样做。

使用方法

简介

Raccoon 基本上由两种协议组成,分别是 ClientEndpoint

  • Client 实例负责将 HTTP 请求排队并返回 Promise 形式的请求。客户端至少需要一个基础 URL(用于构建请求)以及一个用于插入响应的 NSManagedObjectContext
  • Endpoint 实例是提供信息以构建客户端将要发送的请求的对象。Endpoint 只必须实现一个方法,即 request(withBaseURL:) 方法,它将返回完整的请求,该请求使用给定的基础 URL 构建。

在准备使用 Raccoon 之前,您应该熟悉以下内容

  • PromiseKit:至少,您应该熟悉基本的 Promise 处理:thencatchrecover
  • Groot:它用于将 JSON 序列化到 CoreData,因此您的实体必须满足其要求。
  • AlamofireCoreData:至少,您应该阅读有关 Wrapper 的说明(将大 JSON 中的 NSManagedObject 实例序列化)和 Many(将对象的数组序列化)。

入门

为了解释如何使用 Raccoon,我们将构建一个简单的示例。

假设我们有一个 API,包含 2 个方法

  • GET http://sampleapi.com/users/:获取用户列表。
  • GET http://sampleapi.com/users/<id>/:获取指定用户的详细信息。

我们还需要在请求中添加一个 API 密钥作为头部。

为了模拟响应,我们有一个名为 UserNSManagedObject 子类,该类已准备好使用 Groot 进行序列化。

创建客户端

Client 协议有两个必填字段

  • context: NSManagedObjectContext:用于插入响应的上下文。
  • baseURL: String:API 的基础 URL。

因此,我们将创建符合此协议的自己的Client类。

import Raccoon

final class Client: Raccoon.Client {

    let baseURL: String = "http://sampleapi.com/"
    let context: NSManagedObjectContext
    
    init(context: NSManagedObjectContext) {
        self.context = context
    }
}

就这样,现在我们可以通过以下方式创建一个客户端:

let client = Client(context: aContext)

创建端点

Endpoint协议只包含一个方法

func request(withBaseURL baseURL: String) -> DataRequest

该方法由客户端调用以构建请求。

在我们这个例子中,我们将创建一个Endpoint子协议,以帮助我们构建实际的端点。

protocol AppEndpoint: Endpoint {
    var path: String { get }
    var method: Alamofire.HTTPMethod { get }
    var params: Parameters? { get }
    var encoding: Alamofire.ParameterEncoding { get }
}

extension AppEndpoint {
    func request(withBaseURL baseURL: String) -> DataRequest {
        let url = URL(base: baseURL, path: path)!
        
        let headers: HTTPHeaders = ["APIKEY": "MY API KEY"]
        
        return Alamofire.request(url,
                                 method: method,
                                 parameters: params,
                                 encoding: encoding,
                                 headers: headers)
    }
}

一些备注

  • 首先,我们从基本URL和端点路径中构建URL。为了构建URL,我们使用一个针对URL的Raccoon扩展。
  • 接下来,我们将API密钥添加到头信息中。
  • 使用端点提供的信息构建请求并返回。

在我们有了自己的协议之后,我们就可以创建端点。

enum UserEndpoint: AppEndpoint {
    case list
    case detail(id: Int)
    
    // MARK: AppEndpoint
    var method: Alamofire.HTTPMethod { return .get }
    var encoding: Alamofire.ParameterEncoding { return JSONEncoding() }
    var params: Parameters? { return nil }
    
    var path: String {
        switch self {
        case .list:
            return "users"
        case let .detail(id):
            return "users/\(id)/"
        }
    }
}

现在,我们已经准备好发送请求。

排队请求

一旦我们有了ClientEndpoint,排队请求就非常简单。

let client = Client(context: context)

client.enqueue(UserEndpoint.list)
.then { (users: Many<User>) in
    print(users)
}
.catch { error in
    print(error)
}

client.enqueue(UserEndpoint.detail(id: 1))
.then { (user: User) in
    print(user)
}
.catch { error in
    print(error)
}

可取消的排队

如果您想手动取消请求,可以使用cancellableEnqueue方法。这些方法返回一个包含Promisecancel()方法的Cancellable实例。

let client = Client(context: context)

let cancellable: Cancellable<Many<User>> = client.cancellableEnqueue(UserEndpoint.list)

cancellable.promise
.then { (users: Many<User>) in
    print(users)
}
.catch { error in
    print(error)
}


// ... later on

cancellable.cancel()

高级使用

在前面的例子中,我们使用Raccoon的简单模式。它允许一些额外的配置来适应您的REST API设计。

响应包装器

让我们假设我们还有一次调用API的机会,在这里我们执行登录操作

POST http://sampleapi.com/login/ 

此请求的响应是此JSON

{
    "token": "authtoken",
    "user": {"id": 1, "name": "manue"}
}

在这份响应中,我们有两个部分,一个需要“原样”存储的对象(令牌)和一个将被插入上下文中的对象(用户)。

为了处理这些问题,我们创建了一个符合AlamofireCoreData包装器协议的新对象

struct LoginResponse: Wrapper { 
    var token: String!
    var user: User!
    
    init() {}
    
    mutating func map(_ map: Map) {
        token <- map["token"]
        user <- map["user"]
    }
}

现在,我们可以创建一个新端点

struct LoginEndpoint: RestEndpoint {
    let username: String
    let password: String
    
    init(username: String, password: String) {
        self.username = username
        self.password = password
    }
    
    // MARK: AppEndpoint
    var path = "login/"
    var method: Alamofire.HTTPMethod = .post
    var encoding: Alamofire.ParameterEncoding = JSONEncoding()
    var params: Parameters? { 
        return ["username": username, "password": password] 
    }
}    

然后将其入队

let client = Client(context: context)

client.enqueue(LoginEndpoint(username: "username", password: "password")
.then { (response: LoginResponse) in
    print(response.token) // Here you can save your token in the defaults if needed
    print(response.user) // User already inserted in the context
}
.catch { error in
    print(error)
}

自定义JSON序列化

在某些情况下,我们从服务器获得的数据不是正确的格式。甚至可能发生的情况是,有一个XML其中其字段的JSON是我们必须解析的(是的,我找到了这样的事情😅)。为了解决这些问题,客户端协议有一个可选变量,您可以使用它将响应转换为您需要的JSON

var jsonSerializer: DataResponseSerializer<Any>

jsonSerializer只是一个 Alamofire.DataResponseSerializer<Any>。您可以随意构建您的序列化器;唯一的条件是它必须返回您期望的,并且可以被Groot序列化的JSON。

有关如何构建此序列化器的更多信息,请阅读AlamofireCoreData文档中的这一部分

自定义请求

通过使用以下可选的客户端方法,可以改进由端点提供的请求,以在客户端进行改进

func prepare(_ request: DataRequest, for endpoint: Endpoint) -> DataRequest

例如,我们可以添加一个验证器和一个为您请求记录日志的记录器

func prepare(_ request: DataRequest, for endpoint: Endpoint) -> DataRequest {
    return request
        .validate()
        .log()
}

处理承诺

假设我们希望在每次请求成功完成时保存托管对象上下文。我们可以将其添加到每个请求中

client.enqueue(endpoint)
  .then { object: User in
     try client.context.save()
  }

这并不好,您必须在每个请求中添加它。相反,您可以使用客户端协议的可选方法之一

func process<T>(_ promise: Promise<T>, for endpoint: Endpoint) -> Promise<T>

此方法在客户端返回承诺之前调用。默认情况下,它返回承诺本身。

在我们的例子中,您只需将以下这些行添加到您的客户端中

func process<T>(_ promise: Promise<T>, for endpoint: Endpoint) -> Promise<T> {
    return promise.then { response -> T in
        try self.context.save()
        return response
    }
}

您可以在该方法中使用任何需要处理您的承诺,例如从某些错误中“恢复”,或者显示/隐藏状态栏的网络指示器。

许可

Raccoon 可在 MIT 许可证 下使用。

联系

Manuel García-Estañ Martínez
@manueGE