SRNetworkManager is a powerful and flexible networking layer for Swift applications. It provides a generic, protocol-oriented approach to handling API requests, supporting both Combine and async/await paradigms. This package is designed to be easy to use, highly customizable, and fully compatible with Swift 6 and the Sendable protocol.
- 🔗 Generic API Client for various types of network requests
- 🧩 Protocol-Oriented Design for easy customization and extensibility
- ⚡ Support for Combine & async/await
- 🛡️ Robust Error Handling with custom error types
- 🔄 Retry Mechanism for failed requests
- 📤 File Upload Support with progress tracking
- 🔧 Flexible Parameter Encoding (URL & JSON)
- 🧾 Comprehensive Logging System
- 📦 MIME Type Detection for file uploads
- 🔒 Thread-Safe Design with Sendable protocol support
- 🚀 Swift 6 Compatibility
- iOS 13.0+ / macOS 10.15+
- Swift 5.5+
- Xcode 13.0+
Add the following to your Package.swift
file:
dependencies: [
.package(url: "https://github.com/siamakrostami/SRNetworkManager.git", from: "1.0.0")
]
Or use Xcode:
- Go to File > Add Packages...
- Search for:
https://github.com/siamakrostami/SRNetworkManager.git
- Select the latest version and add it to your project.
let client = APIClient() // Basic initialization with default settings
let client = APIClient(qos: .background) // Initialization with custom QoS (Quality of Service)
let client = APIClient(logLevel: .verbose) // Initialization with custom log level
let client = APIClient(qos: .userInitiated, logLevel: .standard) // Initialization with both custom QoS and log level
let client = APIClient(retryHandler: MyCustomRetryHandler()) // Initialization with a custom retry handler
let client = APIClient(decoder: MyCustomDecoder()) // Initialization with a custom decoder
struct UserAPI: NetworkRouter {
typealias Parameters = UserParameters
typealias QueryParameters = UserQueryParameters
var baseURLString: String { "https://api.example.com" }
var method: RequestMethod? { .get }
var path: String { "/users" }
var headers: [String: String]? { HeaderHandler.shared.addAcceptHeaders(type: .applicationJson).addContentTypeHeader(type: .applicationJson).build() }
var params: Parameters? { UserParameters(id: 123) }
var queryParams: QueryParameters? { UserQueryParameters(includeDetails: true) }
}
Or
public protocol SampleRepositoryProtocols: Sendable {
func getInvoice(documentID: String) -> AnyPublisher<SomeModel, NetworkError>
func getInvoice(documentID: String) async throws -> SomeModel
func getReceipt(transactionId: String) -> AnyPublisher<SomeModel, NetworkError>
func getReceipt(transactionId: String) async throws -> SomeModel
}
public final class SampleRepository: Sendable {
// MARK: Lifecycle
public init(client: APIClient) {
self.client = client
}
// MARK: Private
private let client: APIClient
}
extension SampleRepository {
enum Router: NetworkRouter {
case getInvoice(documentID: String)
case getReceipt(transactionId: String)
var path: String {
switch self {
case .getInvoice(let documentID):
return "your/path/\(documentID)"
default:
return "your/path/\(documentID)"
}
}
var method: RequestMethod? {
switch self {
case .getInvoice:
return .get
default:
return .post
}
}
var headers: [String: String]? {
var handler = HeaderHandler.shared
.addAuthorizationHeader()
.addAcceptHeaders(type: .applicationJson)
.addDeviceId()
switch self {
case .getInvoice:
break
default:
handler = handler.addContentTypeHeader(type: .applicationJson)
}
return handler.build()
}
var queryParams: SampleRepositoryQueryParamModel? {
switch self {
case .getInvoice(let trxId):
return SampleRepositoryQueryParamModel(trxId: trxId)
default:
return nil
}
}
var params: SampleRepositoryQueryParamModel? {
switch self {
case .getInvoice(let documentID):
return SampleRepositoryQueryParamModel(
documentId: documentID,
stepId: "Some Id",
subStepId: "Some Id"
)
default:
return nil
}
}
}
}
extension SampleRepository: SampleRepositoryProtocols {
public func getInvoice(documentID: String) -> AnyPublisher<SomeModel, NetworkError> {
client.request(Router.getInvoice(documentID: documentID))
}
public func getInvoice(documentID: String) async throws -> SomeModel {
try await client.asyncRequest(Router.getInvoice(documentID: documentID))
}
public func getReceipt(transactionId: String) -> AnyPublisher<SomeModel, NetworkError> {
client.request(Router.getReceipt(transactionId: transactionId))
}
public func getReceipt(transactionId: String) async throws -> SomeModel {
try await client.asyncRequest(Router.getReceipt(transactionId: transactionId))
}
}
public struct SampleRepositoryQueryParamModel: Codable, Sendable {
public init(documentId: String? = nil,
stepId: String? = nil,
subStepId: String? = nil,
trxId: String? = nil) {
self.documentId = documentId
self.stepId = stepId
self.subStepId = subStepId
self.trxId = trxId
}
public let documentId: String?
public let stepId: String?
public let subStepId: String?
public let trxId: String?
}
Task {
do {
let user: UserAPI = try await client.request(UserAPI())
print("Received user: \(user)")
} catch {
print("Request failed: \(error)")
}
}
let apiClient = APIClient()
apiClient.request(UserAPI())
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
print("Request completed successfully")
case .failure(let error):
print("Request failed with error: \(error)")
}
}, receiveValue: { (response: UserResponse) in
print("Received user: \(response)")
})
.store(in: &cancellables)
let apiClient = APIClient()
let fileData = // ... your file data ...
let endpoint = UploadAPI()
apiClient.uploadRequest(endpoint, withName: "file", data: fileData) { progress in
print("Upload progress: \(progress)")
}
.sink(receiveCompletion: { completion in
// Handle completion
}, receiveValue: { (response: UploadResponse) in
print("Upload completed: \(response)")
})
.store(in: &cancellables)
struct CustomRetryHandler: RetryHandler {
// MARK: Lifecycle
init(numberOfRetries: Int) {
self.numberOfRetries = numberOfRetries
}
// MARK: Public
let numberOfRetries: Int
func shouldRetry(request: URLRequest, error: NetworkError) -> Bool {}
func modifyRequestForRetry(client: APIClient, request: URLRequest, error: NetworkError) -> (URLRequest, NetworkError?) {}
}
To help you get started with SRNetworkManager, we've created a sample SwiftUI app that demonstrates how to use this package in a real-world scenario.
- Setup and usage of
APIClient
- Defining API endpoints using
NetworkRouter
- Making network requests and handling responses in SwiftUI
- Basic error handling
- Clone this repository.
- Navigate to the
Example/SRNetworkManagerExampleApp
directory. - Open
SRNetworkManagerExampleApp.xcodeproj
in Xcode. - Run the project.
We welcome contributions! Please feel free to submit a Pull Request.
SRNetworkManager is available under the MIT license. See the LICENSE file for more details.