https://github.com/denissimon/urlsessionadapter
A Codable wrapper around URLSession for networking.
https://github.com/denissimon/urlsessionadapter
adapter api-adapter api-client async-await carthage cocoapods codable http-client ios macos network-service networking rest-api swift swift-library swift-package-manager urlrequest urlsession xcode
Last synced: 1 day ago
JSON representation
A Codable wrapper around URLSession for networking.
- Host: GitHub
- URL: https://github.com/denissimon/urlsessionadapter
- Owner: denissimon
- License: mit
- Created: 2024-01-23T11:01:23.000Z (almost 2 years ago)
- Default Branch: main
- Last Pushed: 2024-05-22T22:09:41.000Z (over 1 year ago)
- Last Synced: 2024-05-22T22:37:04.552Z (over 1 year ago)
- Topics: adapter, api-adapter, api-client, async-await, carthage, cocoapods, codable, http-client, ios, macos, network-service, networking, rest-api, swift, swift-library, swift-package-manager, urlrequest, urlsession, xcode
- Language: Swift
- Homepage:
- Size: 102 KB
- Stars: 2
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# URLSessionAdapter
[](https://swift.org)
[](https://developer.apple.com/swift/)
A Codable wrapper around URLSession for networking. Includes both APIs: async/await and callbacks.
Supports:
* `Data`, `Upload`, and `Download` 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
Installation
------------
#### Swift Package Manager
To install URLSessionAdapter using [Swift Package Manager](https://swift.org/package-manager), add the following in your `Package.swift`:
```swift
dependencies: [
.package(url: "https://github.com/denissimon/URLSessionAdapter.git", from: "2.2.10")
]
```
Or through Xcode:
```txt
File -> Add Package Dependencies
Enter Package URL: https://github.com/denissimon/URLSessionAdapter
```
#### CocoaPods
To install URLSessionAdapter using [CocoaPods](https://cocoapods.org), add this line to your `Podfile`:
```ruby
pod 'URLSessionAdapter', '~> 2.2'
```
#### Carthage
To install URLSessionAdapter using [Carthage](https://github.com/Carthage/Carthage), add this line to your `Cartfile`:
```ruby
github "denissimon/URLSessionAdapter"
```
#### Manually
Copy folder `URLSessionAdapter` into your project.
Usage
-----
### Define a Decodable/Codable instance
```swift
struct Activity: Decodable {
let id: Int?
let name: String
let description: String
}
```
### Define API endpoints
```swift
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)
}
}
```
### Define API methods
```swift
import URLSessionAdapter
class ActivityRepository {
let networkService: NetworkService
init(networkService: NetworkService) {
self.networkService = networkService
}
func getActivity(id: Int) async -> Result {
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 {
let endpoint = APIEndpoints.createActivity(activity)
guard let request = RequestFactory.request(endpoint) else { ... }
do {
let (data, _) = try await networkService.request(request)
return .success(data)
} catch {
...
}
}
}
```
### API calls
```swift
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:
```swift
let data = try await networkService.fetchFile(url).data
guard let image = UIImage(data: data) else {
...
}
```
Download a file:
```swift
guard try await networkService.downloadFile(url, to: localUrl).result else {
...
}
```
Upload a file:
```swift
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:
```swift
guard let httpResponse = response as? HTTPURLResponse else { return }
assert(httpResponse.statusCode == 200)
```
### Validation
By default, any 300-599 status code returned by the server throws a `NetworkError`:
```swift
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:
```swift
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:
```swift
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 {
...
}
```
### Progress updates
```swift
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](https://github.com/denissimon/URLSessionAdapter/tree/main/Tests/URLSessionAdapterTests) and [iOS-MVVM-Clean-Architecture](https://github.com/denissimon/iOS-MVVM-Clean-Architecture) where this adapter was used.
Public methods
--------------
```swift
// async/await API
func request(_ request: URLRequest, configuration: RequestConfiguration?, delegate: URLSessionTaskDelegate?) async throws -> (data: Data, response: URLResponse)
func request(_ 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(_ 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?
```
License
-------
Licensed under the [MIT license](https://github.com/denissimon/URLSessionAdapter/blob/main/LICENSE)