Dratini 1.1.2

Dratini 1.1.2

测试已测试
语言语言 SwiftSwift
许可 MIT
发布最新版本2017年8月
SwiftSwift 版本3.0
SPM支持 SPM

Kevin Lin 维护。



Dratini 1.1.2

  • 作者
  • Kevin Lin

Dratini CI Status

Dratini 是一个优雅的网络抽象层。
如果您正在寻找使您的网络层更加整洁的解决方案,Dratini 是您的选择。
Dratini 使用协议来定义网络请求、参数和响应,这使得您的网络层更具可读性和可测试性。

特性

  • 基于协议的设计。
  • 自动序列化参数。
  • 响应可通过请求 ID 或响应类型进行观察。
  • UI 非阻塞,因为请求和响应处理在后台线程中进行。
  • 可以通过委托拦截请求和响应。

需求

  • Xcode 8.0+
  • Swift 3.0

依赖项

  • Ditto:它用于将 Swift 对象序列化为与 JSON 兼容的字典,主要用于实现 DefaultQueryString、URLEncodedBodyData 和 JSONBodyData。

使用方法

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 功能更强大

有时您需要用 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)!
    }
}