Networking

网络

Networking 是构建于 Alamofire 之上的网络抽象层。

目录 📦

安装 🎬

Xcode 项目或工作区

要将 Networking 集成到您的 Xcode 项目中作为 Swift Package

  1. 选择 “File -> Swift Packages -> Add Package Dependency...”
  2. 如果您正在使用工作区,请选择项目
  3. 输入 “https://github.com/RonasIT/swift-networking.git

Swift Package Manager

Swift Package Manager 是一个用于自动化 Swift 代码分发的工具,并已集成到 swift 编译器中。Networking 支持在 iOS 平台上使用它。

一旦您设置好 Swift package,添加 Networking 作为依赖项就像将其添加到 Package.swift 的 dependencies 值中一样简单。

dependencies: [
    .package(url: "https://github.com/RonasIT/swift-networking.git", .upToNextMajor(from: "2.0.0"))
]

特性 ✔️

用法 🔨

发起请求

要使用特定的 endpoint 发起请求,您需要继承 NetworkService

import Networking

final class AuthService: NetworkService, AuthServiceProtocol {

    private struct SignInResponse: Decodable {
        let user: User
    }

    @discardableResult
    func signIn(withEmail email: String,
                password: String,
                success: @escaping (User) -> Void,
                failure: @escaping (Error) -> Void) -> CancellableRequest {
        let endpoint = AuthEndpoint.signIn(email: email, password: password)
        return request(for: endpoint, success: { (response: SignInResponse) in
            success(response.user)
        }, failure: { error in
            failure(error)
        })
    }
}

final class MediaService: NetworkService, MediaServiceProtocol {

    struct Media: Decodable {
        let id: UInt64
        let url: URL
    }

    @discardableResult
    func uploadMedia(with data: Data,
                     progress: @escaping Progress,
                     success: @escaping (Media) -> Void,
                     failure: @escaping (Error) -> Void) -> CancellableRequest {
        uploadRequest(
            for: MediaEndpoint.upload(data: data),
            progress: progress,
            success: success,
            failure: failure
        )
    }
}

支持的响应类型

Networking 支持 DecodableDataString[String: Any] 和空响应类型。

此外,您可以使用带有 HTTPURLResponse 的响应来访问状态码和标头

取消请求

CancellableRequest 的实例提供请求取消功能

request.cancel()

通常,取消的请求会因 NSURLErrorCancelled 错误代码而失败。除非您正在使用 GeneralErrorHandler,它会将此错误转换为 GeneralRequestError.cancelled

Endpoint

每个请求都使用特定的 endpoint。Endpoint 包含请求应该发送到哪里以及如何发送的信息。

用法

import Networking

// Customize default values for all endpoints using extension

extension Endpoint {

    var baseURL: URL {
        return AppConfiguration.apiURL
    }

    var headers: [RequestHeader] {
        return [
            RequestHeaders.accept("application/json"),
            RequestHeaders.contentType("application/json")
        ]
    }

    var parameterEncoding: ParameterEncoding {
        return JSONEncoding.default
    }

    var parameters: Parameters? {
        return nil
    }
}

...

// Add endpoint

enum ProfileEndpoint: UploadEndpoint {
    case fetchProfile(Profile.ID)
    case updateAddress(Address)
    case uploadImage(imageData: Data)

    var path: String {
        switch self {
        case .profile(let profileID):
            return "profile/\(profileID)"
        case .updateAddress(let address):
            return "profile/address/\(address.id)"
        case uploadImage:
            return "profile/image"
        }
    }

    var method: HTTPMethod {
        switch self {
        case .profile:
            return .get
        case .updateAddress:
            return .post
        case uploadImage:
            return .post
        }
    }

    var parameters: Parameters? {
        switch self {
        case .updateAddress(let address):
            return address.asDictionary()
        default:
            return nil
        }
    }

    var parameterEncoding: ParameterEncoding {
        return URLEncoding.default
    }

    var imageBodyParts: [ImageBodyPart] {
        switch self {
        case .uploadImage(let imageData):
            return [ImageBodyPart(imageData: imageData)]
        default:
            return []
        }
    }

    var authorizationType: Bool {
        return .bearer
    }
}

注意

网络可达性

Networking 具有内置的 ReachabilityService,可通过 Combine 订阅观察互联网连接状态。

网络可达性用法

import Combine

// Create service
let reachabilityService: ReachabilityServiceProtocol = ReachabilityService()

// Define a Set of subscriptions
var subscriptions: Set<AnyCancellable> = []

// Start monitoring internet connection
reachabilityService.startMonitoring()

// Stop monitoring internet connection
reachabilityService.stopMonitoring()

// Subscribe to internet connection change events
reachabilityService.reachabilityStatusSubject
    .sink { [weak self] status in
        // Handler will be called while subscription is active
    }
    .store(in: &subscriptions)

// Stop receiving the internet connection change events
subscriptions.forEach { $0.cancel() }

// You also can check internet connection directly from service
let isNetworkConnectionAvailable = reachabilityService.isReachable

请求适配

⚠️目前仅支持标头追加⚠️

请求适配允许您在请求中提供附加信息。

请求适配包括

  1. RequestAdapter,它提供请求适配逻辑。
  2. RequestAdaptingService,它管理多个请求适配器的请求适配链。
  3. 您的 NetworkService,它通知请求适配服务关于请求发送/重试。

如果您需要通过请求适配器附加访问令牌,则有一个内置的 TokenRequestAdapter。有关更多信息,请参阅 自动令牌刷新

请求适配用法

  1. 实现您的自定义请求适配器

    import Networking
    import UIKit.UIDevice
    
    final class GeneralRequestAdapter: RequestAdapter {
        // You can use some general headers from `RequestHeaders` enum
        // Let's append some information about the app
        func adapt(_ request: AdaptiveRequest) {
            request.appendHeader(RequestHeaders.dpi(scale: UIScreen.main.scale))
            if let appInfo = Bundle.main.infoDictionary,
               let appVersion = appInfo["CFBundleShortVersionString"] as? String {
                let header = RequestHeaders.userAgent(osVersion: UIDevice.current.  systemVersion, appVersion: appVersion)
                request.appendHeader(header)
            }
        }
    }
  2. 创建请求适配服务,并注入您的请求适配器

    lazy var generalRequestAdaptingService: RequestAdaptingServiceProtocol = {
       return RequestAdaptingService(requestAdapters: [GeneralRequestAdapter()])
    }()
  3. 创建您的 NetworkService 子类,并注入您的请求适配服务

    lazy var profileService: ProfileServiceProtocol = {
        return ProfileService(requestAdaptingService: generalRequestAdaptingService)
    }()

错误处理

此功能为失败的请求提供更有效的错误处理。

错误处理有三个组件

  1. ErrorHandler 提供错误处理逻辑
  2. ErrorHandlingService 存储错误处理程序,管理错误处理链逻辑
  3. 您的 NetworkService,它通知 ErrorHandlingService 关于错误

错误处理程序在许多情况下都很有用。例如,您可以记录错误或将用户重定向到登录屏幕。内置的自动令牌刷新也是使用自定义错误处理程序实现的。

错误处理用法

  1. 创建您自己的错误处理程序
import Networking

final class LoggingErrorHandler: ErrorHandler {
    func handleError(with payload: ErrorPayload, completion: @escaping (ErrorHandlingResult) -> Void) {
        print("Request failure at: \(payload.endpoint.path)")
        print("Error: \(payload.error)")
        print("Response: \(payload.response)")
        // Error payload will be redirected to the next error handler
        completion(.continueErrorHandling(with: payload.error))
    }
}

一旦错误处理完成,您应该使用结果调用完成处理程序,这会影响错误处理链

  1. 创建错误处理服务,并注入您的错误处理程序

    lazy var generalErrorHandlingService: ErrorHandlingServiceProtocol = {
       return ErrorHandlingService(errorHandlers: [LoggingErrorHandler()])
    }()
  2. 将您的错误处理服务传递给 NetworkService 子类

lazy var profileService: ProfileServiceProtocol = {
    return ProfileService(errorHandlingService: generalErrorHandlingService)
}()

GeneralErrorHandler

为了简化某些通用错误的错误处理,任何 ErrorHandlingService 默认都使用内置的 GeneralErrorHandler。您无需手动检查错误代码或响应状态代码。GeneralErrorHandler 会将某些错误映射到 GeneralRequestError。以下是支持的错误列表

public enum GeneralRequestError: Error {
    // For `URLError.Code.notConnectedToInternet`
    case noInternetConnection
    // For `URLError.Code.timedOut`
    case timedOut
    // `AFError` with 401 response status code
    case noAuth
    // `AFError` with 403 response status code
    case forbidden
    // `AFError` with 404 response status code
    case notFound
    // For `URLError.Code.cancelled`
    case cancelled
}

使用 GeneralErrorHandler,您还可以直接从 Endpoint 提供自定义错误。只需实现 func error(for statusCode: StatusCode) -> Error?func error(for urlError: URLError) -> Error?,如下所示。如果这些方法返回 nil,则错误将由 GeneralErrorHandler 提供。

enum ProfileEndpoint: Endpoint {
    case fetchProfile(Profile.ID)
    case uploadImage(imageData: Data)

    func error(for statusCode: StatusCode) -> Error? {
        if case ProfileEndpoint.profile(let profileID) = self {
            switch statusCode {
            case .notFound404:
                return ProfileError.notFound(profileID: profileID)
            default:
                return nil
            }
        }
        return nil
    }

    func error(for urlErrorCode: URLError.Code) -> Error? {
        if case let ProfileEndpoint.uploadImage = self {
            switch urlErrorCode {
            case .timedOut:
                return ProfileError.imageTooLarge
            default:
                return nil
            }
        }
        return nil
    }
}

自动令牌刷新和请求重试

Networking 可以自动刷新访问令牌并重试失败的请求。

此功能有三个组件

  1. UnauthorizedErrorHandler 为状态码为 401 的 “未授权” 错误提供错误处理逻辑
  2. TokenRequestAdapter 在请求发送/重试时提供访问令牌附加功能
  3. 您的服务,它实现 AccessTokenSupervisor 协议,提供访问令牌和访问令牌刷新逻辑

自动令牌刷新用法

  1. 创建您的服务并实现 AccessTokenSupervisor 协议

    import Networking
    
    protocol SessionServiceProtocol: AccessTokenSupervisor {}
    
    final class SessionService: SessionServiceProtocol, NetworkService {
    
        private var token: String?
        private var refreshToken: String?
    
        var accessToken: AccessToken? {
            return token
        }
    
        func refreshAccessToken(success: @escaping () -> Void, failure: @escaping (Error)   -> Void) {
            guard let refreshAccessToken = refreshAccessToken else {
                failure()
                return
            }
            let endpoint = AuthorizationEndpoint.refreshAccessToken(with: refreshToken)
            request(for: endpoint, success: { [weak self] (response: RefreshTokenResponse)  in
                self?.token = response.accessToken
                self?.refreshToken = response.refreshToken
                success()
            }, failure: { [weak self] error in
                self?.token = nil
                failure(error)
            })
        }
    }
  2. 创建带有 TokenRequestAdapterRequestAdaptingService

    lazy var sessionService: SessionServiceProtocol = {
        return SessionService()
    }()
    
    lazy var requestAdaptingService: RequestAdaptingServiceProtocol = {
        let tokenRequestAdapter = TokenRequestAdapter(accessTokenSupervisor: sessionService)
        return RequestAdaptingService(requestAdapters: [tokenRequestAdapter])
    }()
  3. 创建带有 UnauthorizedErrorHandlerErrorHandlingService

    lazy var errorHandlingService: ErrorHandlingServiceProtocol = {
        let unauthorizedErrorHandler = UnauthorizedErrorHandler(accessTokenSupervisor: sessionService)
        return ErrorHandlingService(errorHandlers: [unauthorizedErrorHandler])
    }()
  4. 创建带有您的错误处理和请求适配服务的 NetworkService

lazy var profileService: ProfileServiceProtocol = {
    return ProfileService(requestAdaptingService: requestAdaptingService,
                          errorHandlingService: errorHandlingService)
}()

如果一切正确,您可以忘记应用程序中过期的访问令牌。

注意 未授权错误处理程序不处理不需要授权的 endpoint 的错误。对于这些 endpoint,您仍然会收到未授权错误。

要了解更多信息,请查看示例项目。