Raccoon
Raccoon 是一套协议和工具,整合了 Alamofire,PromiseKit 和 CoreData。
内部,Raccoon 使用 Groot 和 AlamofireCoreData 将 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 基本上由两种协议组成,分别是 Client
和 Endpoint
。
Client
实例负责将 HTTP 请求排队并返回 Promise 形式的请求。客户端至少需要一个基础 URL(用于构建请求)以及一个用于插入响应的NSManagedObjectContext
。Endpoint
实例是提供信息以构建客户端将要发送的请求的对象。Endpoint 只必须实现一个方法,即request(withBaseURL:)
方法,它将返回完整的请求,该请求使用给定的基础 URL 构建。
在准备使用 Raccoon 之前,您应该熟悉以下内容
- PromiseKit:至少,您应该熟悉基本的
Promise
处理:then
、catch
、recover
- Groot:它用于将 JSON 序列化到 CoreData,因此您的实体必须满足其要求。
- AlamofireCoreData:至少,您应该阅读有关
Wrapper
的说明(将大 JSON 中的NSManagedObject
实例序列化)和Many
(将对象的数组序列化)。
入门
为了解释如何使用 Raccoon,我们将构建一个简单的示例。
假设我们有一个 API,包含 2 个方法
GET http://sampleapi.com/users/
:获取用户列表。GET http://sampleapi.com/users/<id>/
:获取指定用户的详细信息。
我们还需要在请求中添加一个 API 密钥作为头部。
为了模拟响应,我们有一个名为 User
的 NSManagedObject
子类,该类已准备好使用 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)/"
}
}
}
现在,我们已经准备好发送请求。
排队请求
一旦我们有了Client
和Endpoint
,排队请求就非常简单。
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
方法。这些方法返回一个包含Promise
和cancel()
方法的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是我们必须解析的(是的,我找到了这样的事情
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 许可证 下使用。