A Codable wrapper around URLSession for networking. Includes both APIs: async/await and callbacks.
Supports:
Data
,Upload
, andDownload
URL session tasks- HTTP methods: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, CONNECT, TRACE, QUERY
- Automatic validation: global or per request based on the received status code
- Delegates to receive progress updates
To install URLSessionAdapter using Swift Package Manager, add the following in your Package.swift
:
dependencies: [
.package(url: "https://github.com/denissimon/URLSessionAdapter.git", from: "2.2.10")
]
Or through Xcode:
File -> Add Package Dependencies
Enter Package URL: https://github.com/denissimon/URLSessionAdapter
To install URLSessionAdapter using CocoaPods, add this line to your Podfile
:
pod 'URLSessionAdapter', '~> 2.2'
To install URLSessionAdapter using Carthage, add this line to your Cartfile
:
github "denissimon/URLSessionAdapter"
Copy folder URLSessionAdapter
into your project.
struct Activity: Decodable {
let id: Int?
let name: String
let description: String
}
import URLSessionAdapter
struct APIEndpoints {
static let baseURL = "https://api.example.com/rest"
static func getActivity(id: Int) -> EndpointType {
let path = "/activities/\(id)/?key=\(Secrets.apiKey)"
return Endpoint(
method: .GET,
baseURL: APIEndpoints.baseURL,
path: path,
params: nil)
}
static func createActivity(_ activity: Activity) -> EndpointType {
let path = "/activities/?key=\(Secrets.apiKey)"
let params = HTTPParams(httpBody: activity.encode(), headerValues: [(
value: "application/json",
forHTTPHeaderField: "Content-Type")
])
return Endpoint(
method: .POST,
baseURL: APIEndpoints.baseURL,
path: path,
params: params)
}
}
import URLSessionAdapter
class ActivityRepository {
let networkService: NetworkService
init(networkService: NetworkService) {
self.networkService = networkService
}
func getActivity(id: Int) async -> Result<Activity, CustomError> {
let endpoint = APIEndpoints.getActivity(id: id)
guard let request = RequestFactory.request(endpoint) else { ... }
do {
let (activity, _) = try await networkService.request(request, type: Activity.self)
return .success(activity)
} catch {
...
}
}
func createActivity(_ activity: Activity) async -> Result<Data, CustomError> {
let endpoint = APIEndpoints.createActivity(activity)
guard let request = RequestFactory.request(endpoint) else { ... }
do {
let (data, _) = try await networkService.request(request)
return .success(data)
} catch {
...
}
}
}
let networkService = NetworkService(urlSession: URLSession.shared)
let activityRepository = ActivityRepository(networkService: networkService)
Task {
let result = await activityRepository.getActivity(id: 1)
switch result {
case .success(let activity):
...
case .failure(let error):
...
}
}
Task {
let result = await activityRepository.createActivity(activity) // will return the id of the created activity
switch result {
case .success(let data):
guard let data = data,
let activityId = Int(String(data: data, encoding: .utf8) ?? "") else { ... }
...
case .failure(let error):
...
}
}
Fetch a file:
let data = try await networkService.fetchFile(url).data
guard let image = UIImage(data: data) else {
...
}
Download a file:
guard try await networkService.downloadFile(url, to: localUrl).result else {
...
}
Upload a file:
let endpoint = SomeAPI.uploadFile(file)
guard let request = RequestFactory.request(endpoint) else { return }
let config = RequestConfiguration(uploadTask: true)
let (data, response) = try await networkService.request(request, configuration: config)
Check the returned status code:
guard let httpResponse = response as? HTTPURLResponse else { return }
assert(httpResponse.statusCode == 200)
By default, any 300-599 status code returned by the server throws a NetworkError
:
do {
let response = try await networkService.request(request) // will return status code 404
...
} catch let networkError as NetworkError {
let description = networkError.error?.localizedDescription
let statusCode = networkError.statusCode // 404
let dataStr = String(data: networkError.data ?? Data(), encoding: .utf8)!
} catch {
// Handling other errors
}
Optionally, this automatic validation can be disabled globally:
networkService.autoValidation = false
do {
let response = try await networkService.request(request) // will return status code 404
let statusCode = (response as? HTTPURLResponse)?.statusCode // 404
let dataStr = String(data: response.data ?? Data(), encoding: .utf8)!
} catch {
...
}
Or it can be disabled for a specific request:
do {
let config = RequestConfiguration(validation: false)
let response = try await networkService.request(request, configuration: config) // will return status code 404
let statusCode = (response as? HTTPURLResponse)?.statusCode // 404
let dataStr = String(data: response.data ?? Data(), encoding: .utf8)!
} catch {
...
}
let progressObserver = ProgressObserver {
print($0.fractionCompleted) // Example output: 0.05 0.0595 1.0
}
do {
let (result, response) = try await networkService.downloadFile(
url,
to: destinationUrl,
delegate: progressObserver
)
...
} catch {
...
}
More usage examples can be found in tests and iOS-MVVM-Clean-Architecture where this adapter was used.
// async/await API
func request(_ request: URLRequest, configuration: RequestConfiguration?, delegate: URLSessionTaskDelegate?) async throws -> (data: Data, response: URLResponse)
func request<T: Decodable>(_ request: URLRequest, type: T.Type, configuration: RequestConfiguration?, delegate: URLSessionTaskDelegate?) async throws -> (decoded: T, response: URLResponse)
func fetchFile(_ url: URL, configuration: RequestConfiguration?, delegate: URLSessionTaskDelegate?) async throws -> (data: Data?, response: URLResponse)
func downloadFile(_ url: URL, to localUrl: URL, configuration: RequestConfiguration?, delegate: URLSessionTaskDelegate?) async throws -> (result: Bool, response: URLResponse)
// callbacks API
func request(_ request: URLRequest, configuration: RequestConfiguration?, completion: @escaping @Sendable (Result<(data: Data?, response: URLResponse?), NetworkError>) -> Void) -> NetworkCancellable?
func request<T: Decodable>(_ request: URLRequest, type: T.Type, configuration: RequestConfiguration?, completion: @escaping @Sendable (Result<(decoded: T, response: URLResponse?), NetworkError>) -> Void) -> NetworkCancellable?
func fetchFile(_ url: URL, configuration: RequestConfiguration?, completion: @escaping @Sendable (Result<(data: Data?, response: URLResponse?), NetworkError>) -> Void) -> NetworkCancellable?
func downloadFile(_ url: URL, to localUrl: URL, configuration: RequestConfiguration?, completion: @escaping @Sendable (Result<(result: Bool, response: URLResponse?), NetworkError>) -> Void) -> NetworkCancellable?
Licensed under the MIT license