Zyigh
Zyigh

Reputation: 425

What type to use for generic decodable class

I have some basics in Swift, and I'm now trying to learn iOS development. I'm currently working in a small app that will ask resource on an API I've made that returns json made from :

struct A : Codable {
    let name: String
    let age: Int
}

struct B : Codable {
    let something: String
}

Both API and app have these structs defined. As I'm always querying the same API, I thought of wrapping the part that ask the API some resources and decode this so I have an instance of the struct to use in my callback. Here's this method :

static func getContent(urlRequest: URLRequest, decodable: Decodable, completion: @escaping (Codable?, ErrorEnum?)->Void) {
    let config = URLSessionConfiguration.default
    let session = URLSession(configuration: config)

    let task = session.dataTask(with: urlRequest) {
        data, response, error in

        guard let data = data else {
            completion(nil, .noData) // Handling errors in an enum
            return 
        }
        let decoder = JSONDecoder()
        if let full = try? decoder.decode(decodable, from: data) {
            completion(full, nil)
        }
    }

    task.resume()
}

My problem concerns the decodable param. This shows an error and prevent me from compiling the app. After finding some resources on StackOverflow, I tried to change the parameters as

static func getContent(urlRequest: URLRequest, decodable: Decodable.Type, completion: @escaping (Codable?, ErrorEnum?)->Void)

I also tried to keep the parameter like this, and instead change inside the decode params

if let full = try? decoder.decode(decodable, from: data) {
    completion(full, nil)
}

but nothing seems to satisfy the compiler... And looking at decode method inside Swift source code didn't help me that much as it requires T.Type where T is Decodable

My wish is to be able to use this as follow :

static func getA() {
    guard let url = URL(string: "http://localhost/a") else { return }

    let urlRequest = URLRequest(url: url)
    getContent(urlRequest: urlRequest, decodable: A.self) {
        a, error in

        guard a = a else { return }
        print(a.name!)
    }
}

Do you have any idea how I could achieve this ? I also don't really know how to call this type of parameters or what to search on google that can lead me to the answer (lack of vocabulary).

Thank you !

Upvotes: 0

Views: 1615

Answers (2)

esyx
esyx

Reputation: 41

You can use this:

func genericRequest<T: Decodable>(_ request: URLRequest, completion: @escaping APIGenericRequestCompletion<T>) {

        Alamofire.request(request).responseData { (response) in


            guard let data = response.data else {
                completion(nil)
                return
            }


            do {
                let decodedObject = try JSONDecoder().decode(T.self, from: data)

                completion(decodedObject)
            } catch {
                completion(nil)
            }

        }
    }

where APIGenericRequestCompletion is:

typealias APIGenericRequestCompletion<T: Decodable> = (_ result: T?) -> Void

Then you use it as:

genericRequest(request) { (decodableObjectResponse) in
            // your code here
}

Upvotes: 1

Mohmmad S
Mohmmad S

Reputation: 5088

try this just add a generic .Type of Codable and use its type as a parameter to pass foo.self

static func getContent<T: Codable>(urlRequest: URLRequest, decodable: T.Type, completion: @escaping (T?, ErrorEnum?)->Void) {
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)

let task = session.dataTask(with: urlRequest) {
    data, response, error in

    guard let data = data else {
        completion(nil, .noData) // Handling errors in an enum
        return
    }
    let decoder = JSONDecoder()
    if let full = try? decoder.decode(decodable, from: data) {
        completion(full, nil)
    }
}
task.resume()
}

Upvotes: 1

Related Questions