iONess

iONess (iOS 网络会话) 是 Home Credit Indonesia iOS 应用程序使用的 iOS 平台的 HTTP 请求助手。它使用 Ergo 作为并发助手和 Promise 管道化工具。

build test SwiftPM Compatible Version License Platform

示例

要运行示例项目,请克隆仓库,并首先从 Example 目录运行 pod install

要求

仅限 Swift Package Manager

安装

Cocoapods

iONess 可通过 CocoaPods 获得。要安装它,只需将以下行添加到您的 Podfile

适用于 iOS 10 或更高版本

pod 'iONess', '~> 2.0'

或适用于 iOS 8 或更高版本

pod 'iONess', '~> 1.2.5'

从 XCode 使用 Swift Package Manager

从 Package.swift 使用 Swift Package Manager

Package.swift 中作为目标依赖项添加。为 iOS 10 或更高版本使用 2.0.2 作为其版本,或为 iOS 8 或更高版本使用 1.2.5

dependencies: [
  .package(url: "https://github.com/oss-homecredit-id/iONess.git", .upToNextMajor(from: "2.0.2"))
]

在您的目标中使用它,如 iONess

 .target(
  name: "MyModule",
  dependencies: ["iONess"]
)

贡献者

许可证

iONess 在 MIT 许可证下可用。有关更多信息,请参阅 LICENSE 文件。

使用示例

基本用法

iONess 旨在简化 HTTP 请求的请求过程。您只需使用 Ness / NetworkSessionManager 类创建请求即可

Ness.default
  .httpRequest(.get, withUrl: "https://myurl.com")
  .dataRequest()
  .then { result in
    // do something with result this will not executed when request failed
  }

或者完全不使用 completion

Ness.default
  .httpRequest(.get, withUrl: "https://myurl.com")
  .dataRequest()

当调用 dataRequest() 数据时,无论它是否有 completion,它总是会立即执行请求。dataRequest() 实际上从 Ergo 返回 Promise 对象,因此您可以始终执行 Ergo Promise 可以执行的所有操作

Ness.default
  .httpRequest(.get, withUrl: "https://myurl.com")
  .dataRequest()
  .then { result in
    // do something with result this will not executed when request failed
  }.handle { error in
    // do something if error occurs
  }.finally { result, error in
    // do something regarding of error or not after request completed
  }

您可以随时 在此处 查看 Ergo 的 Promise 可以执行的操作。

创建请求

要创建请求,您可以执行类似以下操作

Ness.default.httpRequest(.get, withUrl: "https://myurl.com")
  .set(urlParameters: ["param1": "value1", "param2": "value2"])
  .set(headers: ["Authorization": myToken])
  .set(body: dataBody)
  ..
  ..

或使用自定义 URLSession

// create session
var session = URLSession()
session.configuration = myCustomConfig
session.delegateQueue = myOperationQueue
..
..

// create Ness instance
let ness = Ness(with: session)

// create request
ness.httpRequest(.get, withUrl: "https://myurl.com")
  .set(urlParameters: ["param1": "value1", "param2": "value2"])
  .set(headers: ["Authorization": myToken])
  .set(body: dataBody)
  ..
  ..

最好保存 Ness 的实例并重复使用它,因为它将仅使用相同的 URLSession 创建请求,除非您想为另一个请求使用任何其他 URLSession

可用的 HTTP 方法枚举包括

要设置自定义类型的 body,您需要传递实现 HTTPBodyEncoder 对象的自定义类型编码器,以将对象编码为数据

Ness.default.httpRequest(.get, withUrl: "https://myurl.com")
  .set(body: myObject, with encoder: myEndoder) -> Self
  ..
  ..

HTTPBodyEncoder 的声明是

public protocol HTTPBodyEncoder {
  var relatedHeaders: [String: String]? { get }
  func encoder(_ any: Any) throws -> Data
}

relatedHeaders 是与此编码关联的标头,它将自动分配给请求标头。此变量是可选的,因为默认实现返回 nil

有一些不同的默认方法可以使用 iONess 默认 body 编码器设置 body,这些方法是

请求准备就绪后,准备请求,这将返回 Thenable

Ness.default.httpRequest(.get, withUrl: "https://myurl.com")
  .set(urlParameters: ["param1": "value1", "param2": "value2"])
  .set(headers: ["Authorization": myToken])
  .set(body: dataBody)
  ..
  ..
  .dataRequest()

或者对于下载,您需要提供目标位置 URL,您希望将下载的数据保存在那里

Ness.default.httpRequest(.get, withUrl: "https://myurl.com")
  .set(urlParameters: ["param1": "value1", "param2": "value2"])
  .set(headers: ["Authorization": myToken])
  .set(body: dataBody)
  ..
  ..
  .downloadRequest(forSavedLocation: myTargetUrl)

或者对于上传,您需要提供文件位置 URL,您想要上传的文件位于那里

Ness.default.httpRequest(.get, withUrl: "https://myurl.com")
  .set(urlParameters: ["param1": "value1", "param2": "value2"])
  .set(headers: ["Authorization": myToken])
  .set(body: dataBody)
  ..
  ..
  .uploadRequest(forFileLocation: myTargetUrl)

数据请求 Promise

创建数据请求后,您只需使用 then 方法执行请求即可

Ness.default
  .httpRequest(.get, withUrl: "https://myurl.com")
  ..
  ..
  .dataRequest()
  .then { result in
  // do something with result
}

结果是 URLResult 对象,其中包含

HTTPResultMessage 是来自 URLResult 的详细 HTTP 响应

您可以获取 promise 对象或忽略它。它将返回 DataPromise,其中包含请求的状态

let requestPromise = Ness.default
  .httpRequest(.get, withUrl: "https://myurl.com")
  ..
  ..
  .dataRequest()
let status = requestPromise.status

状态包括

您可以使用 drop() 函数取消请求

requestPromise.drop()

由于 promise 基于 Ergo Promise,因此它包含请求的结果(如果已完成)以及发生的错误

// will be nil if the request is not finished yet or if the error occurs
let result = requestPromise.result

// will be nil if an error did not occur or the request is not finished yet
let error = requestPromise.error

// will be true if request completed
print(requestPromise.isCompleted)

上传请求 Promise

Promise 而言,上传请求与数据请求相同。

下载请求 Promise

下载请求与数据请求或上传请求略有不同。下载请求可以暂停和恢复,并且结果也不同

结果是 DownloadResult 对象,其中包含

您可以暂停下载并恢复

request.pause()

let resumeStatus = request.resume()

resume 将返回 ResumeStatus,这是一个枚举

解码数据请求的响应 Body

要解析 body,您可以执行

let decodedBody = try? result.message.parseBody(using: myDecoder)

parseBody 接受任何实现 ResponseDecoder 的对象。 ResponseDecoder 协议的声明如下所示

public protocol ResponseDecoder {
  associatedtype Decoded
  func decode(from data: Data) throws -> Decoded
}

所以您可以执行类似以下操作

class MyResponseDecoder: ResponseDecoder {
  typealias Decoded = MyObject
   
  func decode(from data: Data) throws -> MyObject {
    // do something to decode data into MyObject
  }
}

如果您不想从 Data 解析,可以使用默认的基础解码器

class MyJSONResponseDecoder: BaseJSONDecoder<MyObject> {
  typealias Decoded = MyObject
   
  override func decode(from json: [String: Any]) throws -> MyObject {
    // do something to decode json into MyObject
  }
}

class MyStringResponseDecoder: BaseStringDecoder<MyObject> {
  typealias Decoded = MyObject
   
  override func decode(from string: String) throws -> MyObject {
    // do something to decode string into MyObject
  }
}

HTTPResultMessage 具有自动解析 body 的默认函数,即

验证器

您可以像这样为响应添加验证

Ness.default
  .httpRequest(.get, withUrl: "https://myurl.com")
  ..
  ..
  .validate(statusCodes: 0..<300)
  .validate(shouldHaveHeaders: ["Content-Type": "application/json"])
  .dataRequest()

如果响应无效,则它将出现错误或使用错误分派到 handle 闭包中。

提供的 validate 方法包括

您可以添加自定义验证器来验证 http 响应。验证器的类型是 URLValidator

public protocol ResponseValidator {
  func validate(for response: URLResponse) -> ResponseValidatorResult
}

ResponseValidatorResult 是一个枚举,其中包含

并像这样放置您的自定义 ResponseValidator

Ness.default
  .httpRequest(.get, withUrl: "https://myurl.com")
  ..
  ..
  .validate(using: MyCustomValidator())
  .dataRequest()

如果您只想验证 HTTPURLResponse 并自动使其他响应无效,则可以使用 HTTPValidator

public protocol HTTPValidator: URLValidator {
  func validate(forHttp response: HTTPURLResponse) -> URLValidatorResult
}

请记住,您可以根据需要放置任意数量的验证器,这些验证器将从第一个到最后一个或直到一个验证器返回 invalid 为止,使用所有这些验证器验证响应。如果您不提供任何 URLValidator,则如果有错误或没有来自服务器的响应,则会认为它是无效的,否则,所有响应都将被认为是有效的

NetworkSessionManagerDelegate

您可以使用 NetworkSessionManagerDelegate 在会话级别全局操作请求或操作

public protocol NetworkSessionManagerDelegate: class {
  func ness(_ manager: Ness, willRequest request: URLRequest) -> URLRequest
  func ness(_ manager: Ness, didRequest request: URLRequest) -> Void
}

这两种方法都是可选的。这些方法将运行并适用于

RetryControl

如果您的请求失败,您可以使用 RetryControl 协议控制何时重试

public protocol RetryControl {
  func shouldRetry(
    for request: URLRequest, 
    response: URLResponse?, 
    error: Error, 
    didHaveDecision: (RetryControlDecision) -> Void) -> Void
}

该方法将在请求失败时运行。您唯一需要做的是将 RetryControlDecision 传递到 didHaveDecision 闭包中,这是一个带有成员的枚举

您可以在准备请求时分配 RetryControl

Ness.default
  .httpRequest(.get, withUrl: "https://myurl.com")
  ..
  ..
  .dataRequest(with: myRetryControl)

它也适用于下载或上传请求。

iONess 具有一些默认的 RetryControl,即 CounterRetryControl,其基本算法只是计算失败次数,并在计数器达到 maxCount 时停止重试。要使用它,只需在准备时使用您的 maxCount 或可选地使用 TimeInterval(在重试之前)初始化 CounterRetryControl。例如,如果您想自动重试最多 3 次,每次重试延迟 1 秒

Ness.default
  .httpRequest(.get, withUrl: "https://myurl.com")
  ..
  ..
  .dataRequest(
    with: CounterRetryControl(
      maxRetryCount: 3, 
      timeIntervalBeforeTryToRetry: 1
    )
  )

DuplicatedHandler

如果发生多个重复请求,您可以使用 DuplicatedHandler 处理要执行的操作

public protocol DuplicatedHandler {
  func duplicatedDownload(request: URLRequest, withPreviousCompletion previousCompletion: @escaping URLCompletion<URL>, currentCompletion: @escaping URLCompletion<URL>) -> RequestDuplicatedDecision<URL>
  func duplicatedUpload(request: URLRequest, withPreviousCompletion previousCompletion: @escaping URLCompletion<Data>, currentCompletion: @escaping URLCompletion<Data>) -> RequestDuplicatedDecision<Data>
  func duplicatedData(request: URLRequest, withPreviousCompletion previousCompletion: @escaping URLCompletion<Data>, currentCompletion: @escaping URLCompletion<Data>) -> RequestDuplicatedDecision<Data>
}

它将根据重复请求的类型请求 RequestDuplicatedDecisionRequestDuplicatedDecision 是一个带有成员的枚举

duplicatedHandler 依附于 Ness \ NetworkSessionManager,因此如果您有使用不同 Ness \ NetworkSessionManager 的重复请求,则不应调用它。

要分配 RequestDuplicatedDecision,您只需将其分配给 duplicatedHandler 属性,或者在初始化时添加它即可

// just handler
let ness = Ness(duplicatedHandler: myHandler)

// with session
let ness = Ness(session: mySession, duplicatedHandler: myHandler)

// using property
ness.duplicatedHandler = myHandler

或者您也可以只使用一些默认的处理程序

// just handler
let ness = Ness(onDuplicated: .keepAllCompletion)

// with session
let ness = Ness(session: mySession, onDuplicated: .keepFirstCompletion)

// using property
ness.duplicatedHandler = DefaultDuplicatedHandler.keepLatestCompletion

有 4 个 DefaultDuplicatedHandler

贡献

您知道如何操作,只需克隆并进行 pull request