The Swift Package Manager is a tool for automating the distribution of Swift code and is integrated into the swift compiler. Networking supports its usage on iOS platform.
Once you have your Swift package set up, adding Networking as a dependency is as easy as adding it to the dependencies value of your Package.swift.
Also, you can use response with
HTTPURLResponse
to access the status code and headers:
Response<Decodable> or DecodableResponse<Decodable>
Response<Data> or DataResponse
Response<String> or StringResponse
Response<[String: Any]> or JSONResponse
Response<Void> or EmptyResponse
Cancelling request
Instance of CancellableRequest provides request cancellation:
request.cancel()
As usual, cancelled request fails with NSURLErrorCancelled error code.
Except you are using GeneralErrorHandler, which transforms this error to GeneralRequestError.cancelled.
Endpoint
Each request uses specific endpoint. Endpoint contains an information, where and
how the request should be sent.
Usage
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
}
}
Notes:
By default you should use Endpoint protocol. But if you need to use upload
requests like in example above, use UploadEndpoint,
which has additional imageBodyParts property.
Each endpoint provides authorizationType property. If you are using
TokenRequestAdapter (see request adapting for more),
access token will be attached only for requests with authorized endpoints.
You can also provide custom errors for endpoints using GeneralErrorHandler,
see error handling for more.
Reachability
Networking has built-in ReachabilityService to observe the internet
connection status via Combine subscriptions.
Reachability Usage
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
Request Adapting
⚠️ Currently supports only headers appending ⚠️
Request adapting allows you to provide additional information within request.
Request adapting includes:
RequestAdapters, that provide a request adapting logic.
RequestAdaptingService, that manages a request adapting chain for multiple
request adapters.
Your NetworkService, that notifies request adapting service about request
sending/retrying.
If you need to attach an access token through a request adapter, there is a
built-in TokenRequestAdapter. See automatic token refreshing for more.
Request Adapting Usage
Implement your custom request adapter:
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)
}
}
}
Create request adapting service with your request adapter injected:
lazy var generalRequestAdaptingService: RequestAdaptingServiceProtocol = {
return RequestAdaptingService(requestAdapters: [GeneralRequestAdapter()])
}()
Create your subclass of NetworkService with your request adapting service
injected:
lazy var profileService: ProfileServiceProtocol = {
return ProfileService(requestAdaptingService: generalRequestAdaptingService)
}()
Error Handling
This feature provides more efficient error handling for failed requests.
Your NetworkService, that notifies the ErrorHandlingService about an
error
Error handlers can be useful in many cases. For example, you can log errors or
redirect user to a login screen.
Built-in automatic token refreshing also implemented using custom error handler.
Error Handling Usage
Create your own error handler:
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))
}
}
Once error handling completed, you should call completion handler with
result, which affects error handling chain:
Use continueErrorHandling(with: error) to redirect your error to the
next error handler. If there is no other error handlers, request will be
failed.
Use continueFailure(with: error) to fail request with your error right
now
Use retryNeeded to retry failed request
Create error handling service with your error handler:
lazy var generalErrorHandlingService: ErrorHandlingServiceProtocol = {
return ErrorHandlingService(errorHandlers: [LoggingErrorHandler()])
}()
Pass your error handling service to NetworkService subclass:
lazy var profileService: ProfileServiceProtocol = {
return ProfileService(errorHandlingService: generalErrorHandlingService)
}()
GeneralErrorHandler
To simplify error handling for some general errors, any ErrorHandlingService uses built-in GeneralErrorHandler by default.
You don’t need to check error code or response status code manually. GeneralErrorHandler will map some errors to
GeneralRequestError.
There is a list of supported errors:
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
}
With GeneralErrorHandler you can also provide custom errors right from
Endpoint.
Just implement func error(for statusCode: StatusCode) -> Error? or
func error(for urlError: URLError) -> Error? like below.
If these methods return nil, error will be provided by 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
}
}
Automatic Token Refreshing and Request Retrying
Networking can automatically refresh access tokens and retry failed requests.
There are three components of this feature:
UnauthorizedErrorHandler provides error handling logic for “unauthorized”
errors with 401 status code
TokenRequestAdapter provides access token attaching on request
sending/retrying
Your service, that implements AccessTokenSupervisor protocol, provides
access token and access token refreshing logic
Automatic Token Refreshing Usage
Create your service and implement AccessTokenSupervisor protocol:
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)
})
}
}
Create RequestAdaptingService with TokenRequestAdapter:
lazy var sessionService: SessionServiceProtocol = {
return SessionService()
}()
lazy var requestAdaptingService: RequestAdaptingServiceProtocol = {
let tokenRequestAdapter = TokenRequestAdapter(accessTokenSupervisor: sessionService)
return RequestAdaptingService(requestAdapters: [tokenRequestAdapter])
}()
Create ErrorHandlingService with UnauthorizedErrorHandler:
lazy var errorHandlingService: ErrorHandlingServiceProtocol = {
let unauthorizedErrorHandler = UnauthorizedErrorHandler(accessTokenSupervisor: sessionService)
return ErrorHandlingService(errorHandlers: [unauthorizedErrorHandler])
}()
Create NetworkService with your error handling and request adapting
services:
If all is correct, you can forget about expired access tokens in your app.
Note
Unauthorized error handler doesn’t handle errors for endpoints, which don’t
require authorization. For these endpoints you’ll still receive unauthorized
errors.
Networking
Networking is a network abstraction layer built on top of Alamofire.
Table of contents 📦
Installation 🎬
Xcode project or workspace
To integrate Networking into your Xcode project as Swift Package:
Swift Package Manager
The Swift Package Manager is a tool for automating the distribution of Swift code and is integrated into the swift compiler. Networking supports its usage on iOS platform.
Once you have your Swift package set up, adding Networking as a dependency is as easy as adding it to the dependencies value of your Package.swift.
Features ✔️
GeneralErrorHandler
Usage 🔨
Making a Request
To make requests with specific endpoint you need to subclass
NetworkService
:Supported response types
Networking supports
Decodable
,Data
,String
,[String: Any]
and empty response type.Also, you can use response with
HTTPURLResponse
to access the status code and headers:Response<Decodable>
orDecodableResponse<Decodable>
Response<Data>
orDataResponse
Response<String>
orStringResponse
Response<[String: Any]>
orJSONResponse
Response<Void>
orEmptyResponse
Cancelling request
Instance of
CancellableRequest
provides request cancellation:As usual, cancelled request fails with
NSURLErrorCancelled
error code. Except you are usingGeneralErrorHandler
, which transforms this error toGeneralRequestError.cancelled
.Endpoint
Each request uses specific endpoint. Endpoint contains an information, where and how the request should be sent.
Usage
Notes:
Endpoint
protocol. But if you need to use upload requests like in example above, useUploadEndpoint
, which has additionalimageBodyParts
property.authorizationType
property. If you are usingTokenRequestAdapter
(see request adapting for more), access token will be attached only for requests with authorized endpoints.GeneralErrorHandler
, see error handling for more.Reachability
Networking
has built-inReachabilityService
to observe the internet connection status via Combine subscriptions.Reachability Usage
Request Adapting
⚠️ Currently supports only headers appending ⚠️
Request adapting allows you to provide additional information within request.
Request adapting includes:
RequestAdapter
s, that provide a request adapting logic.RequestAdaptingService
, that manages a request adapting chain for multiple request adapters.NetworkService
, that notifies request adapting service about request sending/retrying.If you need to attach an access token through a request adapter, there is a built-in
TokenRequestAdapter
. See automatic token refreshing for more.Request Adapting Usage
Implement your custom request adapter:
Create request adapting service with your request adapter injected:
Create your subclass of
NetworkService
with your request adapting service injected:Error Handling
This feature provides more efficient error handling for failed requests.
There are three components of error handling:
ErrorHandler
s provide error handling logicErrorHandlingService
stores error handlers, manages error handling chain logicNetworkService
, that notifies theErrorHandlingService
about an errorError handlers can be useful in many cases. For example, you can log errors or redirect user to a login screen. Built-in automatic token refreshing also implemented using custom error handler.
Error Handling Usage
Once error handling completed, you should call completion handler with result, which affects error handling chain:
continueErrorHandling(with: error)
to redirect your error to the next error handler. If there is no other error handlers, request will be failed.continueFailure(with: error)
to fail request with your error right nowretryNeeded
to retry failed requestCreate error handling service with your error handler:
Pass your error handling service to
NetworkService
subclass:GeneralErrorHandler
To simplify error handling for some general errors, any
ErrorHandlingService
uses built-inGeneralErrorHandler
by default. You don’t need to check error code or response status code manually.GeneralErrorHandler
will map some errors toGeneralRequestError
. There is a list of supported errors:With
GeneralErrorHandler
you can also provide custom errors right fromEndpoint
. Just implementfunc error(for statusCode: StatusCode) -> Error?
orfunc error(for urlError: URLError) -> Error?
like below. If these methods returnnil
, error will be provided byGeneralErrorHandler
.Automatic Token Refreshing and Request Retrying
Networking
can automatically refresh access tokens and retry failed requests.There are three components of this feature:
UnauthorizedErrorHandler
provides error handling logic for “unauthorized” errors with 401 status codeTokenRequestAdapter
provides access token attaching on request sending/retryingAccessTokenSupervisor
protocol, provides access token and access token refreshing logicAutomatic Token Refreshing Usage
Create your service and implement
AccessTokenSupervisor
protocol:Create
RequestAdaptingService
withTokenRequestAdapter
:Create
ErrorHandlingService
withUnauthorizedErrorHandler
:Create
NetworkService
with your error handling and request adapting services:If all is correct, you can forget about expired access tokens in your app.
Note Unauthorized error handler doesn’t handle errors for endpoints, which don’t require authorization. For these endpoints you’ll still receive unauthorized errors.
To learn more, please check example project.