A simple, scalable, and testable networking library for Swift.
- 🏗 Modular Architecture: Easily customize and extend the networking layer.
- 💪 Type-Safe Requests: Strong typing for API requests and responses.
- 📦 Lightweight: Built on top of URLSession for minimal overhead.
- 🚀 Modern Concurrency: Supports async/await in Swift.
- 🔄 Interceptor Support: Modify requests and responses globally.
- 🔐 Automatic Token Refresh: Built-in support for token refreshing.
- 📝 Logging: Customizable logging for debugging and monitoring.
- 🔄 Retry Mechanism: Implement retries with customizable policies.
- 🔧 Unit Tested: Comprehensive tests for reliability.
The HTTPClient
protocol is responsible for making raw HTTP requests and returning the data and response. It abstracts the underlying URLSession and can be easily mocked for testing.
URLSessionHTTPClient
is a concrete implementation of HTTPClient
that uses URLSession to make network requests. It can be configured with custom URLSession instances.
RequestExecutor
is a protocol for executing API requests and decoding responses into specified Swift model types.
APIRequestExecutor
is a concrete implementation of RequestExecutor
. It uses the provided HTTPClient
to fetch data and decodes it into the specified model types.
Interceptors allow you to modify requests and responses globally. This is useful for adding headers, logging, error handling, token refreshing, and more.
- RequestInterceptor: A protocol for intercepting and modifying requests and responses.
- AuthorizationInterceptor: Adds authentication credentials to requests.
- TokenRefreshInterceptor: Handles automatic token refreshing upon receiving unauthorized responses.
- LoggingInterceptor: Provides customizable logging for requests and responses.
- RetryInterceptor: Implements retry logic for transient errors with customizable policies.
This networking library provides a convenient way to make API requests using APIRequestExecutor
and RequestExecutor
. Below, you will find examples of how to use these components to create and send API requests.
First, define a custom request type that conforms to the Request
protocol. Include all necessary information for the API endpoint, such as the HTTP method, path, query parameters, and request body.
struct GetUserByIdRequest: Request {
typealias Response = User
let id: String
var method: HTTPMethod {
.get
}
var path: String {
"/users/\(id)"
}
}
let baseURL = URL(string: "https://api.example.com")!
let requestBuilder = DefaultURLRequestBuilder(baseURL: baseURL)
let httpClient = URLSessionHTTPClient()
let apiExecutor = APIRequestExecutor(
requestBuilder: requestBuilder,
httpClient: httpClient
)
To utilize interceptors like authentication, logging, and retries:
// Token provider and refresher
let tokenProvider = { () -> String? in
return TokenStorage.shared.accessToken
}
let tokenRefresher = {
try await AuthService.refreshToken()
}
// Initialize interceptors
let authorizationInterceptor = AuthorizationInterceptor(
tokenProvider: tokenProvider,
headerField: "Authorization",
tokenFormatter: { "Bearer \($0)" }
)
let tokenRefreshInterceptor = TokenRefreshInterceptor(
tokenProvider: tokenProvider,
tokenRefresher: tokenRefresher,
headerField: "Authorization",
tokenFormatter: { "Bearer \($0)" },
statusCodesToRefresh: [401]
)
let loggingInterceptor = LoggingInterceptor()
let retryInterceptor = RetryInterceptor(maxRetryCount: 3)
let interceptors: [RequestInterceptor] = [
tokenRefreshInterceptor,
authorizationInterceptor,
loggingInterceptor,
retryInterceptor
]
// Create an InterceptableHTTPClient
let httpClient = InterceptableHTTPClient(
httpClient: URLSessionHTTPClient(),
interceptors: interceptors,
maxRetryCount: 3
)
let apiExecutor = APIRequestExecutor(
requestBuilder: requestBuilder,
httpClient: httpClient
)
Use the send(_:)
function to send your request.
let getUserRequest = GetUserByIdRequest(id: "123")
do {
let user = try await apiExecutor.send(getUserRequest)
print("User: \(user)")
} catch {
print("Error: \(error)")
}
If your API requires a different authentication scheme, customize the AuthorizationInterceptor
.
let authorizationInterceptor = AuthorizationInterceptor(
tokenProvider: tokenProvider,
headerField: "X-API-Key",
tokenFormatter: { $0 } // Use the token as is
)
let authorizationInterceptor = AuthorizationInterceptor(
tokenProvider: tokenProvider,
headerField: nil, // Do not add to headers
addTokenToQuery: true,
queryParameterName: "api_key"
)
Handle automatic token refreshing with TokenRefreshInterceptor
:
let tokenRefreshInterceptor = TokenRefreshInterceptor(
tokenProvider: tokenProvider,
tokenRefresher: tokenRefresher,
headerField: "Authorization",
tokenFormatter: { "Bearer \($0)" },
statusCodesToRefresh: [401, 403]
)
Use LoggingInterceptor
to log requests and responses:
let loggingInterceptor = LoggingInterceptor()
Implement retries for transient errors:
let retryInterceptor = RetryInterceptor(maxRetryCount: 3, retryDelay: 1.0)
Combine multiple interceptors:
let interceptors: [RequestInterceptor] = [
tokenRefreshInterceptor,
authorizationInterceptor,
loggingInterceptor,
retryInterceptor
]
let httpClient = InterceptableHTTPClient(
httpClient: URLSessionHTTPClient(),
interceptors: interceptors,
maxRetryCount: 3
)
- Interceptors: Create custom interceptors by conforming to the
RequestInterceptor
protocol. - Error Handling: Provide an
APIErrorHandler
to handle API-specific errors. - Custom HTTPClient: Create a class conforming to
HTTPClient
for specific configurations.
- Unit Tests: NetFlex includes comprehensive unit tests for all components.
- Mocking: Use
MockHTTPClient
andMockURLProtocol
for testing. - Testing Interceptors: Write unit tests for interceptors to verify their behavior.
Add NetFlex to your project using Swift Package Manager:
dependencies: [
.package(url: "https://github.com/egzonarifi/NetFlex.git", from: "1.0.0")
]
NetFlex is released under the MIT license. See LICENSE for details.