Networking 是构建于 Alamofire 之上的网络抽象层。
要将 Networking 集成到您的 Xcode 项目中作为 Swift Package
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 支持 Decodable
、Data
、String
、[String: Any]
和空响应类型。
此外,您可以使用带有 HTTPURLResponse
的响应来访问状态码和标头
Response<Decodable>
或 DecodableResponse<Decodable>
Response<Data>
或 DataResponse
Response<String>
或 StringResponse
Response<[String: Any]>
或 JSONResponse
Response<Void>
或 EmptyResponse
CancellableRequest
的实例提供请求取消功能
request.cancel()
通常,取消的请求会因 NSURLErrorCancelled
错误代码而失败。除非您正在使用 GeneralErrorHandler
,它会将此错误转换为 GeneralRequestError.cancelled
。
每个请求都使用特定的 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
}
}
注意
Endpoint
协议。但是,如果您需要像上面示例中那样使用上传请求,请使用 UploadEndpoint
,它具有额外的 imageBodyParts
属性。authorizationType
属性。如果您正在使用 TokenRequestAdapter
(有关更多信息,请参阅 请求适配),则访问令牌仅会附加到具有授权 endpoint 的请求。GeneralErrorHandler
为 endpoint 提供自定义错误,有关更多信息,请参阅 错误处理。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
请求适配允许您在请求中提供附加信息。
请求适配包括
RequestAdapter
,它提供请求适配逻辑。RequestAdaptingService
,它管理多个请求适配器的请求适配链。NetworkService
,它通知请求适配服务关于请求发送/重试。如果您需要通过请求适配器附加访问令牌,则有一个内置的 TokenRequestAdapter
。有关更多信息,请参阅 自动令牌刷新。
实现您的自定义请求适配器
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)
}
}
}
创建请求适配服务,并注入您的请求适配器
lazy var generalRequestAdaptingService: RequestAdaptingServiceProtocol = {
return RequestAdaptingService(requestAdapters: [GeneralRequestAdapter()])
}()
创建您的 NetworkService
子类,并注入您的请求适配服务
lazy var profileService: ProfileServiceProtocol = {
return ProfileService(requestAdaptingService: generalRequestAdaptingService)
}()
此功能为失败的请求提供更有效的错误处理。
错误处理有三个组件
ErrorHandler
提供错误处理逻辑ErrorHandlingService
存储错误处理程序,管理错误处理链逻辑NetworkService
,它通知 ErrorHandlingService
关于错误错误处理程序在许多情况下都很有用。例如,您可以记录错误或将用户重定向到登录屏幕。内置的自动令牌刷新也是使用自定义错误处理程序实现的。
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))
}
}
一旦错误处理完成,您应该使用结果调用完成处理程序,这会影响错误处理链
continueErrorHandling(with: error)
将您的错误重定向到下一个错误处理程序。如果没有其他错误处理程序,请求将失败。continueFailure(with: error)
立即使用您的错误使请求失败retryNeeded
重试失败的请求创建错误处理服务,并注入您的错误处理程序
lazy var generalErrorHandlingService: ErrorHandlingServiceProtocol = {
return ErrorHandlingService(errorHandlers: [LoggingErrorHandler()])
}()
将您的错误处理服务传递给 NetworkService
子类
lazy var profileService: ProfileServiceProtocol = {
return ProfileService(errorHandlingService: generalErrorHandlingService)
}()
为了简化某些通用错误的错误处理,任何 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
可以自动刷新访问令牌并重试失败的请求。
此功能有三个组件
UnauthorizedErrorHandler
为状态码为 401 的 “未授权” 错误提供错误处理逻辑TokenRequestAdapter
在请求发送/重试时提供访问令牌附加功能AccessTokenSupervisor
协议,提供访问令牌和访问令牌刷新逻辑创建您的服务并实现 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)
})
}
}
创建带有 TokenRequestAdapter
的 RequestAdaptingService
lazy var sessionService: SessionServiceProtocol = {
return SessionService()
}()
lazy var requestAdaptingService: RequestAdaptingServiceProtocol = {
let tokenRequestAdapter = TokenRequestAdapter(accessTokenSupervisor: sessionService)
return RequestAdaptingService(requestAdapters: [tokenRequestAdapter])
}()
创建带有 UnauthorizedErrorHandler
的 ErrorHandlingService
lazy var errorHandlingService: ErrorHandlingServiceProtocol = {
let unauthorizedErrorHandler = UnauthorizedErrorHandler(accessTokenSupervisor: sessionService)
return ErrorHandlingService(errorHandlers: [unauthorizedErrorHandler])
}()
创建带有您的错误处理和请求适配服务的 NetworkService
lazy var profileService: ProfileServiceProtocol = {
return ProfileService(requestAdaptingService: requestAdaptingService,
errorHandlingService: errorHandlingService)
}()
如果一切正确,您可以忘记应用程序中过期的访问令牌。
注意 未授权错误处理程序不处理不需要授权的 endpoint 的错误。对于这些 endpoint,您仍然会收到未授权错误。
要了解更多信息,请查看示例项目。