Dratini 是一个优雅的网络抽象层。
如果您正在寻找使您的网络层更加整洁的解决方案,Dratini 是您的选择。
Dratini 使用协议来定义网络请求、参数和响应,这使得您的网络层更具可读性和可测试性。
CocoaPods
pod 'Dratini'
Carthage
github "kevin0571/Dratini"
Swift 包管理器
dependencies: [
.Package(url: "https://github.com/kevin0571/Dratini.git", majorVersion: 1)
]
这里是一些发送请求并观察其响应的基本步骤。
设置请求队列
let requestQueue = RequestQueue(baseURL: URL(string: "http://example.com"))
// Delegate and configuration are not required.
// Set the delegate(RequestQueueDelegate) if you wish to get callbacks for each step.
// RequestQueueConfiguration.default is used if configuration is not specified.
建议保留共享的请求队列
extension RequestQueue {
static let shared = RequestQueue(baseURL: URL(string: "http://example.com"))
}
描述您的请求、参数和响应
struct LogInRequest: Request {
typealias ParametersType = LogInParameters
typealias ResponseType = LogInResponse
var parameters: LogInParameters
func path() -> String {
return "/login"
}
func method() -> HTTPMethod {
return .post
}
}
// There are several built-in Parameters types:
// - DefaultQueryString for query string, it will mostly be used in GET request.
// - URLEncodedBodyData for URL encoded body data.
// - JSONBodyData for JSON format body data.
// - MultipartFormData for multipart form data, it will mostly be used for uploading file.
//
// In order to allow you to keep the naming convention of different platforms,
// property name of DefaultQueryString, URLEncodedBodyData and JSONBodyData will be mapped to other naming convention.
// By default property will be converted to lowercase name separated by underscore,
// e.g. accessToken will be converted to access_token.
// You can set the mapping by overriding "serializableMapping" function.
// See more details in Ditto project's README.
struct LogInParameters: URLEncodedBodyData {
let username: String
let password: String
}
struct LogInResponse: Response {
let username: String
let name: String
init?(data: ResponseData, response: URLResponse) {
// - Use data.data to access raw response data.
// - Use data.jsonObject to access JSON format dictionary.
// - Use data.jsonArray to access JSON format array.
// - Use data.string to access UTF8 string.
guard let username = data.jsonObject["username"] as? String,
let name = data.jsonObject["name"] as? String else {
return nil
}
self.username = username
self.name = name
}
}
发送请求并观察响应
let request = LogInRequest(parameters: LogInParameters(username: username,
password: password))
let requestID = RequestQueue.shared.add(request)
// Observe by using requestID.
// The observer will be removed by RequestQueue after the request is finished.
requestQueue.addObserver(for: requestID) { (result: Result<LogInResponse>) in
guard let response = result.response else {
// Show error message
return
}
// Update UI by using response.username and response.name
}
// Observe a specific response type.
// The observer is owned by an owner. The owner is held weakly by RequestQueue,
// thus the observer will be removed if owner is released.
requestQueue.addObserver(ownedBy: self) { [weak self] (result: Result<LogInResponse>) in
// ...
}
// NOTE: observer callback is called in main thread.
有时您需要用 Dratini 做更多的事情,以下是一些您可能需要的特性,例如上传文件、拦截请求和响应的不同状态。
上传文件
let data = MultipartFormData()
// Append file with fileURL
data.append(fileURL: fileURL, withName: name, fileName: fileName, mimeType: "application/x-plist")
// Append raw file data
data.append(data: fileData, withName: name, fileName: fileName, mimeType: "application/x-plist")
// Assume we've created UploadFileRequest
let request = UploadFileRequest(parameters: data)
// Send out request
// ...
拦截请求的状态
// Conform to Request with RequestDelegate to get callbacks of different states.
struct LogInRequest: Request, RequestDelegate {
// ...
func requestWillSend(_ urlRequest: inout URLRequest) {
// Called before request is sent out.
// You are able to modify the URLRequest: update HTTP header for example.
}
func requestDidSend(_ urlRequest: URLRequest) {
// Called after request is sent out.
}
func request(_ urlRequest: URLRequest, didFailWith error: DRError) {
// Called when request is failed to be sent out or response is failed to be created.
}
}
在创建响应之前验证响应并拦截响应的状态
struct LogInResponse: Response, ResponseDelegate {
// ...
// Validate the response before it's created.
static func validate(_ response: URLResponse) -> Bool {
guard let httpResponse = response as? HTTPURLResponse else {
return true
}
return httpResponse.statusCode >= 200 &&
httpResponse.statusCode < 300 &&
httpResponse.allHeaderFields["Token"] != nil
}
// Called after response is created.
func responseDidReceive(_ response: URLResponse) {
guard let httpResponse = response as? HTTPURLResponse,
let token = httpResponse.allHeaderFields["Token"] else {
return nil
}
// Save your token
}
}
对于所有请求和响应具有通用逻辑有时是必要的,RequestQueueDelegate为您提供解决方案
class MyRequestQueueDelegate: RequestQueueDelegate {
public func requestQueue(_ requestQueue: RequestQueue, willSend request: inout URLRequest) {
// Called before each request is sent out.
}
public func requestQueue(_ requestQueue: RequestQueue, didSend request: URLRequest) {
// Called after each request is sent out.
}
public func requestQueue(_ requestQueue: RequestQueue, didFailWith request: URLRequest, error: DRError) {
// Called when request is failed to be sent out or response is failed to be created.
}
public func requestQueue(_ requestQueue: RequestQueue, didReceive response: URLResponse) {
// Called after response is created.
}
}
extension RequestQueue {
// Set delegate when creating RequestQueue.
static let shared = RequestQueue(delegate: MyRequestQueueDelegate(), baseURL: URL(string: "http://example.com")!)
}
检查请求是否完成并取消它
let isFinished = RequestQueue.shared.isFinished(requestID)
RequestQueue.shared.cancel(requestID)
如果您确实不需要参数或响应,您可以使用以下方法:
EmptyParameters
EmptyResponse
如果您希望自定义查询字符串或正文数据编码,您可以通过采用QueryString或BodyData协议实现自己的解决方案。
struct MyBodyData: BodyData {
let string: String
var contentType: String {
return "my-content-type"
}
func encode() throws -> Data {
return string.data(using: .utf8)!
}
}