Trying to get data with Alamofire and swift 5 - swift

I want to load some data with Alamofire
Thats is my class
struct Welcome: Codable {
let count: Int
let next: String
let previous: String
let results: [Übungen]
}
// MARK: - Result
struct Übungen: Codable {
let id, category: Int
let resultDescription, name, nameOriginal: String
let muscles, musclesSecondary, equipment: Int
let creationDate: String
let language: Int
let uuid: String
let variations: Int
next would be my Protocol
protocol ÜbungenGateway {
func fetch(completion: #escaping (Result<[Übungen], Error>) -> Void)
}
and as the last part comes the Alamofireclass
class ÜbungenAlamofireGateway : ÜbungenGateway {
let url = "https://wger.de/api/v2/"
func fetch(completion: #escaping (Result<[Übungen], Error>) -> Void) {
AF.request(url)
.validate()
.responseJSON {
switch $0.result{
case .success:
do {
let decoder = JSONDecoder()
let excercise = try! decoder.decode(Übungen.self, from: $0.data!)
completion(.success(excercise.name))
}catch {
completion(.failure(error))
}
case .failure(let error):
completion(.failure(error))
}
}
}
}
my Problem is that i get the following error:
"Cannot convert value of type 'String' to expected argument type '[Übungen]'"
at the line
completion(.success(excercise.name))
and i dont know why.
I hope someone could help me out.

So the first issue is with type in completion block in fetch method.
func fetch(completion: #escaping (Result<[Übungen], Error>) -> Void)
Expected returned type in completion is a list of the Übungen object, but you are trying to pass name (which is a string) from Übungen object completion(.success(excercise.name)). That's why you are getting this error.
The second think is that you are calling "https://wger.de/api/v2/" endpoint and from documentation the response will be a list of links and some properties from your object does not exist in the response.

Related

Method cannot be marked #objc because the type of the parameter 3 cannot be represented in Objective-C - Error type not recognized in #objc func?

I’m trying to call this function from an objective C class but am running into an issue. I get the error "Method cannot be marked #objc because the type of the parameter 3 cannot be represented in Objective-C"
I’m assuming the issue is with the “Error” parameter but don’t understand what the issue is exactly. Everything I’ve read has been dealing with Swift 1 or 2 but this is in Swift 5.
class NetworkManager {
struct DataModel: Decodable {
let id: String
let carID: String
let ownerId: String
let locationId: String?
let status: String
}
static let shared = NetworkManager()
#objc static func fetchCarWarranty(for carID: String, ownerID: String, completed: #escaping (Result<[DataModel], Error>) -> Void) {
let urlString = APIURL
let session = URLSession.shared
let url = URL(string: urlString)!
var request = URLRequest(url: url)
let task = session.dataTask(with: request) { data, response, error in
if let _ = error {
completed(.failure(error!))
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
completed(.failure(error!))
return
}
guard let data = data else {
return
}
do {
let decoder = JSONDecoder()
let dataModel = try decoder.decode([DataModel].self, from: data)
completed(Result.success(dataModel))
} catch {
completed(.failure(error))
}
}
task.resume()
}
}
I've thought of calling fetchCarWarranty from a second Swift function but not sure if this is a good practice to follow or not.
Also, how can I get the value from this function if I'm calling it from an objective-c file/ class?
You can't use Result in Objective-C, you need a closure like the one for dataTask(with:completion:), with optional parameters like error to indicate it's not in "error case":
//Call that one when using Objective-C
#objc static func fetchCarWarranty(for carID: String, ownerID: String, completed: #escaping (([DataModel]?, Error?) -> Void)) {
fetchCarWarranty(for: carID, ownerID: ownerID) { result in
switch result {
case .success(let models):
completed(models, nil)
case .failure(let error):
completed(nil, error)
}
}
}
//Call that one when using Swift
static func fetchCarWarranty(for carID: String, ownerID: String, completed: #escaping (Result<[DataModel], Error>) -> Void) {
// Your method "Swifty" code
}
I did this for an app with legacy Objective-C code. You call in the end the Swifty method with Result internally. so the code does the same.
You could also for the Objective-C compatible one have to closures:
#objc static func fetchCarWarranty(for carID: String, ownerID: String, success: #escaping (([DataModel]) -> Void), failure: #escaping ((Error) -> Void)) {
fetchCarWarranty(for: carID, ownerID: ownerID { result in
switch result {
case .success(let models):
success(models)
case .failure(let error):
failure(error)
}
}
}
Now, the issue is that DataModel is a struct. If you replace that with:
// With temp [String] parameter instead of [DataModel]
#objc static func fetchCarWarranty(for carID: String, ownerID: String, completed: #escaping (([String]?, Error?) -> Void)) {
fetchCarWarranty(for: carID, ownerID: ownerID) { result in
switch result {
case .success(let models):
completed(["models"], nil) //Fake output
case .failure(let error):
completed(nil, error)
}
}
}
It works, because String is visible by Objective-C. But you can't see a Swift struct from Objective-C. See the documentation, or one the related questions:
Is there a way to use Swift structs in Objective-C without making them Classes?
How to pass a Swift struct as a parameter to an Objective-C method
How to use Swift struct in Objective-C

I get the error message #escaping attribute only applies to function types, even though it worked previously

I am currently trying to make an app that tracks prices of crypto. While making an API caller I ran into the issue that it won't let me use escaping even when it previously worked. I am not sure if it is because an update to Xcode or not. Currently on version 12.5.1. Any help would be appreciated.
import Foundation
final class APICall{
static let instance = APICall()
private struct defaults{
static let apiKey = ""
static let assets = ""
}
private init(){
}
public func getCryptoValues(completion: #escaping Result<[String], Error>) -> Void{
guard let apiUrl = URL(string: defaults.assets + "?apikey=" + defaults.apiKey)
else{return}
let task = URLSession.shared.dataTask(with: apiUrl){ data, _, error in
guard let data = data, error == nil else{
return
}
do{
let cryptos = try JSONDecoder().decode([CryptoCoin].self, from: data)
completion(.success(cryptos))
}
catch{
completion(.failure(error))
}
}
task.resume()
}
}
As #vadian mentioned above, the reason for this issue is that your completion parameter should be a function to have #escaping attribute in front of it.
A function that takes the form (Input) -> Output
and for this case it should be completion: #escaping (Input) -> Output
The issue should go away if you change function signature from
public func getCryptoValues(completion: #escaping Result<[String], Error>) -> Void{
to
public func getCryptoValues(completion: #escaping (Result<[String], Error>) -> Void) {

How to struct's value such that all views can access its values in Swift/SwiftUI?

Currently I am decoding a JSON response from an API and storing it into a struct "IPGeolocation". I want to be able to store this data in a variable or return an instance of this struct such that I can access the values in views.
Struct:
struct IPGeolocation: Decodable {
var location: Coordinates
var date: String
var sunrise: String
var sunset: String
var moonrise: String
var moonset: String
}
struct Coordinates: Decodable{
var latitude: Double
var longitude: Double
}
URL extension with function getResult:
extension URL {
func getResult<T: Decodable>(completion: #escaping (Result<T, Error>) -> Void) {
URLSession.shared.dataTask(with: self) { data, response, error in
guard let data = data, error == nil else {
completion(.failure(error!))
return
}
do {
completion(.success(try data.decodedObject()))
} catch {
completion(.failure(error))
}
}.resume()
}
}
Function that retrieves and decodes the data:
func getMoonTimes(lat: Double, long: Double) -> Void{
urlComponents.queryItems = queryItems
let url = urlComponents.url!
url.getResult { (result: Result<IPGeolocation, Error>) in
switch result {
case let .success(result):
print("Printing returned results")
print(result)
case let .failure(error):
print(error)
}
}
}
My goal is to take the decoded information and assign it to my struct to be used in views after. The results variable is already an IPGeolocation struct once the function runs. My question lies in the best way to store it or even return it if necessary.
Would it make sense to have getResult return an IPGeolocation? Are there better/different ways?
Thanks!
EDIT: I made changes thanks to help from below comments from Leo Dabus.
func getMoonTimes(completion: #escaping (IPGeolocation?,Error?) -> Void) {
print("STARTING FUNC")
let locationViewModel = LocationViewModel()
let apiKey = "AKEY"
let latitudeString:String = String(locationViewModel.userLatitude)
let longitudeString:String = String(locationViewModel.userLongitude)
var urlComponents = URLComponents(string: "https://api.ipgeolocation.io/astronomy?")!
let queryItems = [URLQueryItem(name: "apiKey", value: apiKey),
URLQueryItem(name: "lat", value: latitudeString),
URLQueryItem(name: "long", value: longitudeString)]
urlComponents.queryItems = queryItems
urlComponents.url?.getResult { (result: Result<IPGeolocation, Error>) in
switch result {
case let .success(geolocation):
completion(geolocation, nil)
case let .failure(error):
completion(nil, error)
}
}
}
To call this method from my view:
struct MoonPhaseView: View {
getMoonTimes(){geolocation, error in
guard let geolocation = geolocation else {
print("error:", error ?? "nil")
return
}
}
...
...
...
IMO it would be better to return result and deal with the error. Note that you are assigning the same name result which you shouldn't. Change it to case let .success(geolocation). What you need is to add a completion handler to your method because the request runs asynchronously and return an option coordinate and an optional error as well:
Note that I am not sure if you want to get only the location (coordinates) or the whole structure with your method. But the main point is to add a completion handler to your method and pass the property that you want or the whole structure.
func getMoonTimes(for queryItems: [URLQueryItem], completion: #escaping (Coordinates?,Error?) -> Void) {
urlComponents.queryItems = queryItems
urlComponents.url?.getResult { (result: Result<IPGeolocation, Error>) in
switch result {
case let .success(geolocation):
completion(geolocation.location, nil)
case let .failure(error):
completion(nil, error)
}
}
}
Usage:
getMoonTimes(for: queryItems) { location, error in
guard let location = location else {
print("error:", error ?? "nil")
return
}
print("Location:", location)
print("Latitude:", location.latitude)
print("Longitude:", location.longitude)
}

Swift generic Serializable protocol

I noticed that I have a lot of repeated code for getting/sending JSON to my app's API, but the only thing that is different is what entity is being (de)serialized. So I came up with the following design (for brevity only the GET method):
HTTPClient.swift
func getEntityJSON<T: JSONSerializable>(
type: T.Type,
url: String,
completionHandler: #escaping (_ result: T?,
_ headers: [String: String]?,
_ statusCode: Int?,
_ error: Error?) -> Void) {
HttpClient.sharedInstance().getJSON(url, completionHandler: { (jsonData, headers, statusCode, error) in
if let error = error {
completionHandler(nil, headers, statusCode, error)
} else {
if let entityData = jsonData {
completionHandler(T.fromJSON(data: entityData), headers, statusCode, nil)
}
}
})
}
to intend to use it like this:
HTTPClient.sharedInstance().getEntity(type: Translation, url: url) { (translation, _, _, _) in
// do stuff with Translation instance
}
and here is the JSONSerializable protocol:
import Foundation
import SwiftyJSON
protocol Serializable: Codable {
static func deserialize<T: Codable>(data: Data) -> T?
func serialize() -> Data?
}
extension Serializable {
static func deserialize<T: Decodable>(data: Data) -> T? {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .formatted(DateFormatter.iso8601Full)
return try? decoder.decode(T.self, from: data)
}
func serialize() -> Data? {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
encoder.dateEncodingStrategy = .formatted(DateFormatter.iso8601Full)
return try? encoder.encode(self)
}
}
protocol JSONSerializable: Serializable {
func toJSON() -> JSON?
static func fromJSON<T>(data: JSON) -> T?
}
extension JSONSerializable {
func toJSON() -> JSON? {
if let data = self.serialize() {
return try? JSON(data: data)
} else {
return nil
}
}
}
This allows me to define a struct like this:
Translation.swift
struct Translation: Hashable, Identifiable, JSONSerializable {
var id: UUID
...
static func fromJSON(data: JSON) -> Translation? {
and I will get serialize, deserialize, toJSON functions. But the compiler complains that Translation does not conform to JSONSerializable, due to lack of:
static func fromJSON<T>(data: JSON) -> T?. I thought I would be able to implement that function with a concrete type, Translation in this case.
I would like to be both able to do Translations.fromJSON(data: data) as well as T.fromJSON(data: data). How can I achieve this?
Generics (<T>) means that your method can work with any type that the caller (not the callee, not the implementer, not anyone else) specifies. In the case of fromJSON, this is clearly not the case. Any concrete implementation of fromJSON only works with T being the enclosing type, so fromJSON is not suitable to be generic.
When a method "only works with T being the enclosing type", it is the use case of Self types:
protocol JSONSerializable: Serializable {
func toJSON() -> JSON?
static func fromJSON(data: JSON) -> Self?
}
This way, your Translation implementation would conform to the protocol.
Similarly, deserialise should also be declared as:
static func deserialize(data: Data) -> Self?
If you actually want a JSONSerialisable that can turn JSON into any type that the caller wants, then it's less of a JSONSerialisable, and more of a JSONSerialiser, and it doesn't make much sense to inherit from Codable in that case.
OK I ended up implementing a JSONSerializer, because I couldn't get it to work for collections of items to be (de)serialized. This is how it currently looks:
import Foundation
import SwiftyJSON
protocol JSONSerializable {
static func fromJSON(data: JSON) -> Self?
}
class JSONSerializer {
static func deserialize<T: Decodable>(_ type: T.Type, data: Data) -> T? {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .formatted(DateFormatter.iso8601Full)
return try? decoder.decode(type, from: data)
}
static func serialize<T: Encodable>(_ object: T) -> Data? {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
encoder.dateEncodingStrategy = .formatted(DateFormatter.iso8601Full)
return try? encoder.encode(object)
}
static func fromJSON<T: JSONSerializable>(type: T.Type, data: JSON) -> T? {
T.fromJSON(data: data)
}
static func toJSON<T: Encodable>(_ object: T) -> JSON? {
if let data = Self.serialize(object) {
return try? JSON(data: data)
} else {
return nil
}
}
}
I will look into the fromJSON function later to see if I can completely get rid of JSONSerializable and implemented it in the JSONSerializer like this:
static func fromJSON<T: Decodable>(type: T.Type, data: JSON) -> T? {
if let jsonData = try? data.rawData() {
return Self.deserialize(type, data: jsonData)
}
return nil
}

Method to download using generics asks for more context

I wrote a method using generics in order to make it reusable to any kind of download. It downloads json arrays and returns a generic object or an error.
This is my class to download json arrays from server:
import Foundation
enum Result<T> {
case success(T)
case failure(Error)
}
class LTLNetworkClient: NSObject {
fileprivate var session : URLSession
fileprivate var objectTask : URLSessionDataTask?
override init() {
let config = URLSessionConfiguration.ephemeral
config.timeoutIntervalForResource = 5
config.timeoutIntervalForRequest = 5
self.session = URLSession.init(configuration: config)
}
/**
Download asynchronously json object from Server and returns it into generic data models
*/
func getData<K: Codable>(request: URLRequest, completion: #escaping (Result<[K]>) -> Void) {
let sessionDataTask = URLSession.shared.dataTask(with: request) { (responseData, response, responseError) in
if let jsonData = responseData {
let decoder = JSONDecoder()
do {
let response = try decoder.decode([K].self, from: jsonData)
let result: Result<[K]> = Result.success(response)
completion(result)
} catch {
completion(.failure(error))
}
} else if let error = responseError {
completion(.failure(error))
} else {
let error = NSError(domain: "Cannot form jsonData with Response Data", code: 501, userInfo: nil)
completion(.failure(error))
}
}
sessionDataTask.resume()
}
}
And this is how I call the method and the error I get:
Does anyone knows how to fix it and why this error appears?
Many thx in advance
Here's your method declaration:
func getData<K: Codable>(request: URLRequest, completion: #escaping (Result<[K]>) -> Void)
This method has a type parameter, K, which is only used in the type of the completion argument. When the compiler sees a call to getData, it has to deduce a concrete type for K based on the argument type of the closure you pass as completion.
And here's your call:
networkClient.getData(request: urlRequest) { (result) in
// Do stuff
}
In this call, the completion closure is { (result) in }. What is the type of result? There is no information given about it, so all the compiler knows is that result must be type Result<[K]> for some type K. It has no way to deduce a concrete type for K, so it emits an error.
You can make the type explicit like this:
networkClient.getData(request: urlRequest) { (_ result: Result<[SomeConcreteCodableType]>) -> Void in
// Do stuff
}
where SomeConcreteCodableType is some concrete type that conforms to Codable. The Codable conformance is required because of the constraint in getData's declaration.
A different solution is to change getData to take another argument that lets the caller specify K directly:
func getArray<K: Codable>(of: K.Type, for request: URLRequest,
completion: #escaping (Result<[K]>) -> Void) {
...
}
Then you call it like this:
networkClient.getArray(of: SomeConcreteCodableType.self, for: request) { result in
// Do stuff
}