Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/artemkalinovsky/legatus
Combine π - driven REST API client, based on Alamofire π₯
https://github.com/artemkalinovsky/legatus
alamofire api api-rest apimanager combine jsonparser networklayer networklibrary swift swift-framework swiftui xmlparser
Last synced: 4 months ago
JSON representation
Combine π - driven REST API client, based on Alamofire π₯
- Host: GitHub
- URL: https://github.com/artemkalinovsky/legatus
- Owner: artemkalinovsky
- License: mit
- Archived: true
- Created: 2020-01-03T16:47:53.000Z (about 5 years ago)
- Default Branch: main
- Last Pushed: 2024-07-06T17:20:17.000Z (7 months ago)
- Last Synced: 2024-09-30T15:52:02.561Z (4 months ago)
- Topics: alamofire, api, api-rest, apimanager, combine, jsonparser, networklayer, networklibrary, swift, swift-framework, swiftui, xmlparser
- Language: Swift
- Homepage:
- Size: 140 KB
- Stars: 38
- Watchers: 4
- Forks: 3
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
![swift workflow](https://github.com/artemkalinovsky/Legatus/actions/workflows/swift.yml/badge.svg)
# Legatus π
A legatus (anglicised as legate) was a high-ranking Roman military officer in the Roman Army, equivalent to a modern high-ranking general officer. Initially used to delegate power, the term became formalised under Augustus as the officer in command of a legion.
Legatus was also a term for an ambassador of the Roman Republic who was appointed by the senate for a mission (legatio) to a foreign nation, as well as for ambassadors who came to Rome from other countries.## Intro π¬
The basic idea of *Legatus* is that we want some network abstraction layer that
sufficiently encapsulates actually calling Alamofire directly.Also, it would be cool to have network layer, that will compatible with ***SwiftUI*** out-of-the-box π¦, isn't it?π§
Luckily, *Legatus* was implemented with `Combine` framework and have couple of fancy methods, that will allow you to `assign(to:on:)` your response models right to `@Published` properties. Neat!π€©
### Some awesome features of Legatusπ:
* SOLID design (e.g.: `APIClient` don't stores and configures requests, each request is encapsulated in separate entity).
* Easy retrying of requests.
* Elegant and flexible canceling of requests.
* Reachability tracking.
* Support JSON and XML response formats.
* ***Combine*** extension.
* ***Swift Concurrency*** support.*Legatus* is inspired by [Moya](https://github.com/Moya/Moya).
## Project Status π€
I consider it's ready for production use.
Any contributions (pull requests, questions, propositions) are always welcome!π## Requirements π
* Swift 5.6+
* macOS 12+
* iOS 15+
* tvOS 15+
* watchOS 8+## Installation π¦
* #### Swift Package Manager
You can use Xcode SPM GUI: *File -> Swift Packages -> Add Package Dependency -> Pick "Up to Next Major Version 2.0.0"*.
Or add the following to your `Package.swift` file:
``` swift
.package(url: "https://github.com/artemkalinovsky/Legatus.git", .upToNextMajor(from: Version("2.0.0")))
```and then specify `"Legatus"` as a dependency of the Target in which you wish to use Legatus.
Here's an example `PackageDescription` :``` swift
// swift-tools-version:5.6
import PackageDescriptionlet package = Package(
name: "MyPackage",
products: [
.library(
name: "MyPackage",
targets: ["MyPackage"]),
],
dependencies: [
.package(url: "https://github.com/artemkalinovsky/Legatus.git", .upToNextMajor(from: Version("2.0.0")))
],
targets: [
.target(
name: "MyPackage",
dependencies: ["Legatus"])
]
)
```## Basic Usage π§βπ»
Let's suppose we want to fetch list of users from JSON and response is look like this:
``` json
{
"results":[
{
"name":{
"first":"brad",
"last":"gibson"
},
"email":"[email protected]"
}
]
}
```* #### Setup
1. Create `APIClient` :
``` swift
let apiClient = APIClient(baseURL: URL(string: "https://webservice.com/api/")!)
```2. Create response model:
``` swift
import Foundation
import Legatusfinal class User: Decodable {
let firstName: String?
let lastName: String?
let email: String?enum CodingKeys: String, CodingKey {
case name
case email
}enum NameKeys: String, CodingKey {
case firstName = "first"
case lastName = "last"
}init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
email = try values.decodeIfPresent(String.self, forKey: .email)let name = try values.nestedContainer(keyedBy: NameKeys.self, forKey: .name)
firstName = try name.decodeIfPresent(String.self, forKey: .firstName)
lastName = try name.decodeIfPresent(String.self, forKey: .lastName)
}
}
```3. Create request with endpoint path and desired reponse deserializer:
``` swift
import Foundation
import Legatusfinal class UsersApiRequest: DeserializeableRequest {
var path: String {
"users"
}
var deserializer: ResponseDeserializer<[User]> {
JSONDeserializer.collectionDeserializer(keyPath: "results")
}}
```* #### Perfrom created request
``` swift
apiClient.executeRequest(request: UsersApiRequest()) { result in }
```VoilΓ !π§βπ¨
## Advanced Usage π€π»
* #### Working with CoreData models.
To deserialize your response right to CoreData `NSManagedObject` , just call designated initializer firstly:
``` swift
import Foundation
import CoreData
import Legatus@objc(CoreDataObject)
public class CoreDataObject: NSManagedObject, Decodable {required convenience public init(from decoder: Decoder) throws {
self.init(context: NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType))//TODO: implement decoding
}}
```* #### Working with [Realm](https://github.com/realm/realm-cocoa) models.
To deserialize your response right to Realm `Object` subclass:
``` swift
import Foundation
import RealmSwift
import Legatusfinal class RealmObject: Object, Decodable {
@objc dynamic var name = ""
required init() {
super.init()
}convenience init(from decoder: Decoder) throws {
self.init()//TODO: implement decoding
}
}
```Same functionality available for `XMLDeserializer` too.
* #### Retrying requests
If you want to retry previously failed request, just provide count of desiried retry times:
``` swift
apiClient.executeRequest(request: UsersApiRequest(), retries: 3) { result in }
```* #### Request cancelation
To cancel certain request, you have to store it's cancelation token and call `cancel()` method.
``` swift
let cancelationToken = apiClient.executeRequest(request: UsersApiRequest()) { result in }
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) {
cancelationToken.cancel()
}
```Also, you can cancel all active requests:
``` swift
apiClient.cancelAllRequests()
```## Combine Extension π
While working with SwiftUI, where most of UI updates based on *Combine* mechanisms under the hood, it's very convenient to get
`Publisher` as request result for future transformations and assigns:``` swift
@Published var users = [User]()
var subscriptions = Set()apiClient
.responsePublisher(request: UsersApiRequest())
.catch { _ in return Just([User]())}
.assign(to: \.users, on: self)
.store(in: &subscriptions)
```## Swift Concurrency Extension π¦
``` swift
do {
let usersResponse = try await apiClient.executeRequest(request: UsersApiRequest())
} catch {
// handle error
}
```## Apps using Legatus π±
- [PinPlace](https://apps.apple.com/ua/app/pinplace/id1571349149)
## Credits π
* [Moya](https://github.com/Moya/Moya)
* [Combine Community](https://github.com/CombineCommunity)
* @0111b for [JSONDecoder-Keypath](https://github.com/0111b/JSONDecoder-Keypath)
* @drmohundro for [SWXMLHash](https://github.com/drmohundro/SWXMLHash)## License π
Legatus is released under an MIT license. See [LICENCE](https://github.com/artemkalinovsky/Legatus/blob/master/LICENSE) for more information.