HTTPTaskPublisher 3.0.0

HTTPTaskPublisher 3.0.0

Nayanda Haberty 维护。



  • hainayanda

Swift HTTP Task Publisher

Swift HTTP Task Publisher 是对 DataTaskPublisher 的一种强大扩展,旨在简化并增强 Swift 项目的 HTTP 请求处理。

Codacy Badge build test Version License Platform

示例项目

要运行示例项目,请按照以下步骤操作

  1. 克隆仓库。
  2. 导航到 "Example" 目录。
  3. 运行 pod install
  4. 打开 Xcode 工作空间并运行示例应用。

要求

  • Swift 5.5 或更高版本
  • iOS 13.0 或更高版本
  • macOS 10.15 或更高版本
  • TVOS 13.0 或更高版本
  • watchOS 8.0 或更高版本
  • Xcode 13 或更高版本

安装

使用 CocoaPods 安装

要使用 CocoaPods 安装 Swift HTTP Task Publisher,请将以下行添加到您的 Podfile 中

pod 'HTTPTaskPublisher', '~> 2.0'

使用 Swift Package Manager (Xcode) 安装

  • 前往 Xcode 菜单 文件 > Swift 包 > 添加包依赖
  • https://github.com/hainayanda/HTTPTaskPublisher.git 作为 Swift 包 URL 添加
  • 将规则设置为 版本,选择 最高到下一个主要版本 选项,并使用 2.0.0 作为版本。
  • 点击“下一步”,等待获取包。

从 Package.swift 使用 Swift Package Manager

要将 Swift HTTP Task Publisher 作为依赖项添加到 Swift Package Manager 项目中,请在 Package.swift 文件的依赖项中添加它

dependencies: [
    .package(url: "https://github.com/hainayanda/HTTPTaskPublisher.git", .upToNextMajor(from: "2.0.0"))
]

然后,在目标的依赖项中添加 HTTPTaskPublisher

 .target(
    name: "YourTargetName",
    dependencies: [
        .product(name: "HTTPTaskPublisher", package: "HTTPTaskPublisher"),
    ]
)

作者

HTTPTaskPublisher 由Nayanda Haberty开发。您可以联系作者:[email protected]

许可证

HTTPTaskPublisher 在MIT许可证下发布。更详细的信息,请参阅LICENSE文件。

基本用法

执行HTTP请求时,与使用DataTaskPublisher类似

var myRequest: URLRequest(url: url)

// ...

let cancellable = URLSession.shared.httpTaskPublisher(for: myRequest)
    .sink { completion in
        // do something after complete
    } receiveValue: { response in
        // do something with the response
    }

响应类似于以下这样的元组

(data: Data, response: HTTPURLResponse)

并将发出一个类型为HTTPURLError的错误

public indirect enum HTTPURLError: Error {
    case failWhileRetry(error: Error, orignalError: HTTPURLError)
    case failToRetry(reason: String, orignalError: HTTPURLError)
    case failWhileAdapt(request: URLRequest, originalError: Error)
    case failDecode(data: Data, response: HTTPURLResponse, decodeError: Error)
    case failValidation(reason: String, data: Data, response: HTTPURLResponse)
    case expectHTTPResponse(data: Data, response: URLResponse)
    case urlError(URLError)
    case error(Error)
}

您可以按如下方式将数据解码为所需类型:

URLSession.shared.httpTaskPublisher(for: myRequest)
    .decode(type: MyObject.self, decoder: JSONDecoder())
    .sink { ... }

然后它将生成一个类似以下的元组

(decoded: MyObject, response: HTTPURLResponse)

重复处理

Swift HTTP任务发布者将存储任何正在进行中的请求在URLSession中,直到其完成。当相同的请求仍在进行中时,您可以通过在创建新的HTTPDataTaskPublisher时传递DuplicationHandling枚举来控制您想要如何执行请求。

URLSession.shared.httpTaskPublisher(for: myRequest, whenDuplicated: .useCurrentIfPossible)
    .sink { ... }

DuplicationHandling是一个枚举,其声明如下:

public enum DuplicationHandling {
    case alwaysCreateNew
    case useCurrentIfPossible
    case dropIfDuplicated
}

默认选项为alwaysCreateNew

验证

您可以对请求添加验证,以便在响应验证失败时抛出错误

URLSession.shared.httpTaskPublisher(for: myRequest)
    .validate { data, httpUrlResponse in
        // validating
        return .invalid(reason: "no reason")
    }
    .sink { ... }

闭包将在请求成功并生成 DataHTTPURLResponse 后被调用。您可以对结果进行验证并返回 HTTPDataTaskValidation 作为验证结果。然后,将使用该结果来确定请求是否应传递给订阅者或抛出 HTTPURLError.failValidation(reason:data:response:)

HTTPDataTaskValidation 是一个像这样声明的枚举:

public enum HTTPDataTaskValidation: Equatable {
    case valid
    case invalid(reason: String)
}

如果要仅验证状态码,可以这样操作

URLSession.shared.httpTaskPublisher(for: myRequest)
    .allowed(statusCode: 200)
    .sink { ... }

// or like this
URLSession.shared.httpTaskPublisher(for: myRequest)
    .allowed(statusCodes: 200..<300)
    .sink { ... }

// or like this
URLSession.shared.httpTaskPublisher(for: myRequest)
    .allowed(statusCodes: 200, 201)
    .sink { ... }

// or using array like this
URLSession.shared.httpTaskPublisher(for: myRequest)
    .allowed(statusCodes: [200, 201])
    .sink { ... }

如果您需要进行更复杂的验证,只需实现 HTTPDataTaskValidator

struct MyValidator: HTTPDataTaskValidator { 
    func httpDataTaskIsValid(for data: Data, response: HTTPURLResponse) -> HTTPDataTaskValidation {
        // validating
        return .invalid(reason: "no reason")
    }
}

并将其传递

URLSession.shared.httpTaskPublisher(for: myRequest)
    .validate(using: MyValidator())
    .sink { ... }

重试

要控制何时进行重试,可以这样做

URLSession.shared.httpTaskPublisher(for: myRequest)
    .retryDecision { error, request in
        // deciding
        return .drop
     }
    .sink { ... }

当请求失败并产生 HTTPURLError 时,将调用闭包。您可以根据此结果并返回 HTTPDataTaskRetryDecision 作为重试决策结果。然后,将使用该结果来确定请求是否应重试。

HTTPDataTaskRetryDecision 是一个像这样声明的枚举:

public enum HTTPDataTaskRetryDecision: Equatable {
    case retry
    case dropWithReason(reason: String)
    case drop
}

如果要进行更复杂的重试,可以实现 HTTPDataTaskRetrier

struct MyRetrier: HTTPDataTaskRetrier {
    
    func httpDataTaskShouldRetry(for error: HTTPURLError, request: URLRequest) async throws -> HTTPDataTaskRetryDecision {
        guard let code = error.statusCode, code == 401 else { 
            return .drop
        }
        let token = try await refreshToken()

        // create a new request with token
        // ...
        // ...

        return .retryWithNewRequest(newRequest)
    }
}

并将其传递

URLSession.shared.httpTaskPublisher(for: myRequest)
    .retrier(using: MyRetrier())
    .sink { ... }

适应

您可以在发送请求之前这样修改您的请求

URLSession.shared.httpTaskPublisher(for: myRequest, adapter: myAdapter)
    .sink { ... }

使用 HTTPDataTaskAdapter,它是这样实现的

struct MyAdapter: HTTPDataTaskAdapter {
    
    func httpDataTaskAdapt(for request: URLRequest) async throws -> URLRequest {
        let myToken = getTokenFromCache() ?? try await refreshToken()

        // create a new request with token
        // ...
        // ...
            
        return newRequest
    }
}

组合

由于HTTPTaskPublisher是使用Combine框架创建的,因此您可以像这样执行任何Combine允许的操作

import Combine

let payloads = try await URLSession.shared.httpTaskPublisher(for: myRequest)
    .retry(2)
    .map { $0.decoded.payloads }
    .sink { ... }

贡献

您知道怎么做,只需克隆并提交一个pull request即可