iONess 2.0.2

iONess 2.0.2

nayanda1 维护。



iONess 2.0.2

  • 作者
  • nayanda

iONess

iONess (iOS Network Session) 是 Home Credit Indonesia iOS App 在 iOS 平台上使用的 HTTP 请求助手。它使用 Ergo 作为并发助手和承诺流水线。

build test SwiftPM Compatible Version License Platform

示例

要运行示例项目,请先克隆仓库,然后从 Example 目录中运行 pod install

需求

  • Swift 5.0 或更高版本(或当使用 Swift Package Manager 时为 5.1)
  • iOS 10 或更高版本(最新版本)
  • iOS 8 或更高版本(1.2.5 版本)

仅 Swift Package Manager

  • macOS 10.10 或更高版本
  • tvOS 10 或更高版本

安装

Cocoapods

iONess 通过 CocoaPods 提供使用。要安装它,只需将以下行添加到您的 Podfile 中

对于 iOS 10 或更高版本

pod 'iONess', '~> 2.0'

或对于 iOS 8 或更高版本

pod 'iONess', '~> 1.2.5'

从 XCode 的 Swift Package Manager

  • 使用 Xcode 菜单 文件 > Swift 包 > 添加包依赖项 来添加它
  • https://github.com/oss-homecredit-id/iONess.git 作为 Swift 包 URL 添加
  • 版本 菜单中设置规则,选择 下一个版本 选项,并将 2.0.2 作为其版本放置于 iOS 10 或更高版本中,或 1.2.5 作为 iOS 8 或更高版本中的版本
  • 单击“下一步”并等待

从 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请求过程。您只需使用NessNetworkSessionManager类创建请求即可。

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

或者完全不进行完成

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

当调用dataRequest()方法时,无论是否存在完成,它都将立即执行请求。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这里了解更多关于其承诺内容的信息。

创建请求

要创建请求,您可以这样做

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方法枚举类型包括

  • post
  • get
  • put
  • patch
  • delete
  • head
  • connect
  • options
  • trace
  • none如果您不想包含HTTP方法头
  • 自定义(String)

如果需要设置自定义的正文类型,则需要传递实现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默认正文编码器设置正文有一些不同的默认方法

  • func set(body: Data) -> Self
  • func set(stringBody: String, encoding: String.Encoding = .utf8) -> Self
  • func set(jsonBody: [String: Any]) -> Self
  • func set(arrayJsonBody: [Any]) -> Self
  • func set(jsonEncodable: EObject) -> Self
  • func set(arrayJsonEncodable: [EObject]) -> Self

请求准备就绪后,准备请求,将返回 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 对象,其中包含

  • urlResponse: URLResponse?,表示原始响应,您可以在这里了解文档
  • error: Error?,表示如果有错误,将是一个错误。在成功响应中将为 nil
  • responseData: Data?,表示响应体的原始数据
  • isFailed: Bool,如果请求失败则为 true
  • isSucceed: Bool,如果请求成功则为 true
  • httpMessage: HTTPResultMessage?,表示请求的响应消息。如果结果是 HTTP 结果,则为 nil

HTTPResultMessage 是从 URLResult 获得的详细 HTTP 响应

  • url: HTTPURLCompatible,表示响应的原始 URL
  • headers: Header,表示响应的头部
  • body: Data?,表示响应体
  • statusCode: Int,表示响应的状态码

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

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

状态如下

  • running(Float),包含从 0 - 1 的请求进度百分比
  • dropped
  • 空闲
  • completed(HTTPURLResponse),包含已完成的响应
  • error(Error),如果在请求中发生错误,则包含错误

您可以使用 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 对象,它包含

  • urlResponse: URLResponse?,表示原始响应,您可以在这里了解文档
  • error: Error?,表示如果有错误,将是一个错误。在成功响应中将为 nil
  • dataLocalURL: URL? 表示已下载数据的存储位置
  • isFailed: Bool,如果请求失败则为 true
  • isSucceed: Bool,如果请求成功则为 true

您可以选择暂停和恢复下载

request.pause()

let resumeStatus = request.resume()

恢复将返回一个枚举类型的 ResumeStatus

  • 已恢复
  • 恢复失败

解码数据请求的响应体

为了解析响应体,您可以这样做:

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 提供默认函数来自动解析响应体

  • func parseBody(toStringEndoded encoding: String.Encoding = .utf8) throws -> String
  • func parseJSONBody() throws -> [String: Any]
  • func parseArrayJSONBody() throws -> [Any]
  • func parseJSONBody<DObject: Decodable>() throws -> DObject
  • func parseArrayJSONBody<DObject: Decodable>() throws -> [DObject]
  • func parseJSONBody<DOBject: Decodable>(forType type: DOBject.Type) throws -> DOBject
  • func parseArrayJSONBody<DObject: Decodable>(forType type: DObject.Type) throws -> [DObject]

验证器

您可以为响应添加验证,如下所示:

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

如果响应无效,则会有错误或将其分派到 handle 闭包中并包含错误。

提供的方法是:

  • validate(statusCode: Int) -> Self
  • validate(statusCodes: Range<Int>) -> Self
  • validate(shouldHaveHeaders headers: [String:String]) -> Self
  • validate(_ validation: HeaderValidator.Validation, _ headers: [String: String]) -> Self

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

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

ResponseValidatorResult 是一个枚举,包含以下内容:

  • valid
  • invalid
  • invalidWithReason(String):有自定义原因,这将作为 NetworkSessionError 错误的描述

按照如下方式添加您自定义的 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
}

这两个方法都是可选的。这些方法将运行并具有功能

  • ness(_: , willRequest: )在执行任何请求之前运行。您可以在其中操作URLRequest对象并返回它,或在请求之前执行任何操作并返回当前的URLRequest
  • ness(_: , didRequest: )在请求执行后运行,但在请求完成之前不运行。

RetryControl

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

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

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

  • noRetry将自动失败请求
  • retryAfter(TimeInterval)将在TimeInterval后重试相同的请求
  • retry将立即重试相同的请求

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

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

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

iONess有一些默认的RetryControl,即CounterRetryControl,其基本算法是仅计算失败次数,达到最大计数时停止重试。要使用它,只需在准备时初始化CounterRetryControl,并使用您的最大计数,或者可选地在重试前使用TimeInterval。例如,如果要自动重试最多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是一个枚举,具有以下成员

  • dropAndRequestAgain将放弃之前的请求,并使用当前的完成执行新的请求
  • dropAndRequestAgainWithCompletion((Param?, URLResponse?, Error?) -> Void)将放弃之前的请求,并使用自定义完成执行新的请求
  • ignoreCurrentCompletion将忽略当前完成,因此当请求完成时,它将仅运行第一个请求完成
  • useCurrentCompletion将忽略之前完成,因此当请求完成时,它将仅运行最新的请求完成
  • useBothCompletion 将保留之前的完成状态,所以当请求完成时,它将只运行所有请求的完成
  • useCompletion((Param?, URLResponse?, Error?) -> Void) 将忽略所有完成,并使用自定义的完成

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

  • dropPreviousRequest 将放弃之前请求,并使用当前的完成情况重新发起一个新请求
  • keepAllCompletion 将保留之前的完成状态,所以当请求完成时,它将仅运行所有请求的完成
  • keepFirstCompletion 将忽略当前的完成,所以当请求完成时,它将仅运行第一个请求的完成
  • keepLatestCompletion 将忽略之前的完成,所以当请求完成时,它将仅运行最新的请求完成

贡献

你知道怎么做,只需克隆并发起 pull request