A comprehensive, thread-safe networking library for Swift applications with support for both Combine and async/await programming models.
- 🔄 Dual Programming Models: Support for both Combine and async/await
- 🛡️ Thread Safety: All operations are thread-safe with proper synchronization
- 🔄 Retry Logic: Configurable retry strategies for failed requests
- 📤 Upload Support: File upload with progress tracking
- 🌊 Streaming: Support for streaming responses
- 📡 Network Monitoring: Real-time network connectivity and VPN detection
- 🔧 Error Handling: Rich error types with proper mapping
- 📝 Logging: Comprehensive request/response logging with multiple levels
- 🔐 Authentication: Built-in support for various authentication methods
- 📦 Parameter Encoding: Support for JSON, URL-encoded, and multipart form data
- iOS 13.0+
- macOS 13.0+
- tvOS 13.0+
- watchOS 7.0+
- Swift 5.9+
Add the following dependency to your Package.swift
:
dependencies: [
.package(url: "https://github.com/yourusername/SRNetworkManager.git", from: "1.0.0")
]
Or add it directly in Xcode:
- File → Add Package Dependencies
- Enter the repository URL
- Select the version you want to use
import SRNetworkManager
// Create a client with default configuration
let client = APIClient()
// Define your endpoint
struct GetUsersEndpoint: NetworkRouter {
var baseURLString: String { "https://api.example.com" }
var path: String { "/users" }
var method: RequestMethod? { .get }
}
// Make a request
client.request(GetUsersEndpoint())
.sink(
receiveCompletion: { completion in
if case .failure(let error) = completion {
print("Error: \(error)")
}
},
receiveValue: { (users: [User]) in
print("Received \(users.count) users")
}
)
.store(in: &cancellables)
// Using async/await
do {
let users: [User] = try await client.request(GetUsersEndpoint())
print("Received \(users.count) users")
} catch {
print("Error: \(error)")
}
The main client for making network requests.
let client = APIClient(
configuration: .default,
qos: .userInitiated,
logLevel: .standard,
decoder: JSONDecoder(),
retryHandler: DefaultRetryHandler(numberOfRetries: 3)
)
Define your API endpoints with type safety.
struct CreateUserEndpoint: NetworkRouter {
struct Parameters: Codable {
let name: String
let email: String
}
var baseURLString: String { "https://api.example.com" }
var path: String { "/users" }
var method: RequestMethod? { .post }
var params: Parameters? { parameters }
private let parameters: Parameters
init(name: String, email: String) {
self.parameters = Parameters(name: name, email: email)
}
}
Monitor network connectivity changes in real-time.
let monitor = NetworkMonitor()
monitor.startMonitoring()
monitor.status
.sink { connectivity in
switch connectivity {
case .disconnected:
print("Network disconnected")
case .connected(let networkType):
print("Connected via \(networkType)")
}
}
.store(in: &cancellables)
let endpoint = UploadEndpoint()
let imageData = UIImage().jpegData(compressionQuality: 0.8)
client.uploadRequest(endpoint, withName: "image", data: imageData) { progress in
print("Upload progress: \(Int(progress * 100))%")
}
.sink(
receiveCompletion: { completion in
print("Upload completed")
},
receiveValue: { (response: UploadResponse) in
print("Upload successful: \(response.url)")
}
)
.store(in: &cancellables)
client.streamRequest(StreamingEndpoint())
.sink(
receiveCompletion: { completion in
print("Stream completed")
},
receiveValue: { (chunk: DataChunk) in
print("Received chunk: \(chunk)")
}
)
.store(in: &cancellables)
struct CustomRetryHandler: RetryHandler {
let numberOfRetries: Int
func shouldRetry(request: URLRequest, error: NetworkError) -> Bool {
switch error {
case .urlError(let urlError):
return urlError.code == .notConnectedToInternet ||
urlError.code == .timedOut
case .customError(let statusCode, _):
return statusCode >= 500
default:
return false
}
}
func modifyRequestForRetry(client: APIClient, request: URLRequest, error: NetworkError) -> (URLRequest, NetworkError?) {
var newRequest = request
newRequest.setValue("retry", forHTTPHeaderField: "X-Retry-Attempt")
return (newRequest, nil)
}
// Implement async methods...
}
let client = APIClient(retryHandler: CustomRetryHandler(numberOfRetries: 3))
// Bearer token authentication
let headers = HeaderHandler.shared
.addAuthorizationHeader(type: .bearer(token: "your-token"))
.addAcceptHeaders(type: .applicationJson)
.build()
struct AuthenticatedEndpoint: NetworkRouter {
var baseURLString: String { "https://api.example.com" }
var path: String { "/protected" }
var method: RequestMethod? { .get }
var headers: [String: String]? { headers }
}
class NetworkAwareService {
private let client = APIClient()
private let monitor = NetworkMonitor()
private var pendingRequests: [() -> Void] = []
init() {
monitor.startMonitoring()
setupNetworkHandling()
}
private func setupNetworkHandling() {
monitor.status
.sink { [weak self] connectivity in
if case .connected = connectivity {
self?.processPendingRequests()
}
}
.store(in: &cancellables)
}
func makeRequest<T: Codable>(_ endpoint: NetworkRouter) -> AnyPublisher<T, NetworkError> {
if case .connected = monitor.currentStatus {
return client.request(endpoint)
} else {
return Future { promise in
self.pendingRequests.append {
self.client.request(endpoint)
.sink(receiveCompletion: { promise($0) }, receiveValue: { promise(.success($0)) })
.store(in: &self.cancellables)
}
}
.eraseToAnyPublisher()
}
}
}
let client = APIClient(logLevel: .verbose) // .none, .minimal, .standard, .verbose
let headers = HeaderHandler.shared
.addContentTypeHeader(type: .applicationJson)
.addAcceptHeaders(type: .applicationJson)
.addAcceptLanguageHeaders(type: .en)
.addAcceptEncodingHeaders(type: .gzip)
.addCustomHeader(name: "X-API-Key", value: "your-api-key")
.build()
let vpnChecker = VPNChecker()
if vpnChecker.isVPNActive() {
print("VPN is connected")
// Handle VPN-specific logic
}
The library provides comprehensive error handling with specific error types:
switch networkError {
case .urlError(let urlError):
print("Network error: \(urlError.localizedDescription)")
case .decodingError(let decodingError):
print("Decoding error: \(decodingError)")
case .customError(let statusCode, let data):
print("Server error: \(statusCode)")
case .responseError(let error):
print("Response error: \(error)")
case .unknown:
print("Unknown error occurred")
}
All operations in SRNetworkManager are thread-safe:
- APIClient: Uses dedicated dispatch queues for synchronization
- NetworkMonitor: Thread-safe status updates and continuation management
- HeaderHandler: Synchronized header operations
- UploadProgressDelegate: Thread-safe progress handling
- Efficient Monitoring: Uses system-level network monitoring
- Minimal Overhead: Low CPU and memory usage
- Background Operation: Can operate in background queues
- Battery Impact: Minimal battery impact from monitoring
#if DEBUG
let logLevel: LogLevel = .verbose
let retryHandler = DefaultRetryHandler(numberOfRetries: 3)
#else
let logLevel: LogLevel = .none
let retryHandler = DefaultRetryHandler(numberOfRetries: 1)
#endif
let client = APIClient(
logLevel: logLevel,
retryHandler: retryHandler
)
client.request(endpoint)
.sink(
receiveCompletion: { completion in
if case .failure(let error) = completion {
switch error {
case .urlError(let urlError):
if urlError.code == .notConnectedToInternet {
showOfflineMessage()
}
case .customError(let statusCode, _):
if statusCode == 401 {
handleUnauthorized()
}
default:
showGenericError()
}
}
},
receiveValue: { response in
handleSuccess(response)
}
)
.store(in: &cancellables)
class NetworkService {
private var cancellables = Set<AnyCancellable>()
private let client = APIClient()
func makeRequest() {
client.request(endpoint)
.sink(receiveCompletion: { ... }, receiveValue: { ... })
.store(in: &cancellables) // Store to prevent cancellation
}
}
APIClient
: Main client for network requestsNetworkRouter
: Protocol for defining API endpointsNetworkError
: Comprehensive error typesRetryHandler
: Protocol for custom retry logicNetworkMonitor
: Real-time network monitoringVPNChecker
: VPN connection detection
GET
,POST
,PUT
,PATCH
,DELETE
,HEAD
,TRACE
wifi
,cellular
,ethernet
,vpn
,other
none
,minimal
,standard
,verbose
applicationJson
,urlEncoded
,formData
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
This project is licensed under the MIT License - see the LICENSE file for details.
- Built with Apple's Network framework
- Inspired by modern networking patterns
- Designed for performance and reliability