Search code examples
iosswifthttpswift5urlsession

Can I pass in a dynamic @escapeing(Result<[CLASS], APIError>) argument to my function to reuse my code?


I am new to swift and struggling a bit. I have several GET queries which all end up doing the same just using another class (Target iOS 10.3, Swift 5).

This method receives a bulk of datasets and returns them through the completion handler. Currently I have this code multiple times for each type but I can imagine that there is not a more efficient way.

CLASS marks the only differences:

func getMultiple (completion: @escaping(Result<[CLASS], APIError>) -> Void) {

let data = try JSONDecoder().decode([CLASS].self, from: data!)

Here my method:

func getMultiple (completion: @escaping(Result<[CLASS], APIError>) -> Void) {
        do {
            urlRequest.httpMethod = "GET"
            
            let task = URLSession.shared.dataTask(with: urlRequest) {(data, response, error) in
                if let error = error {
                    print("error: \(error)")
                }
                else {
                    if let httpResponse = response as? HTTPURLResponse {
                        switch httpResponse.statusCode {
                        case 200:
                            do {
                                let data = try JSONDecoder().decode([CLASS].self, from: data!)
                                completion(.success(data))
                                return
                            } catch {
                                print("caught: \(error)")
                            }
                            
                        default:
                            do {
                                let data = try JSONDecoder().decode(ServerMessage.self, from: data!)
                                completion(.failure(
                                    APIError(
                                        code: httpResponse.statusCode,
                                        msg: data)
                                    ))
                            } catch {
                                print("caught: \(error)")
                            }
                            return
                        }
                    }
                }
            }
            task.resume()
        }
    }

Solution

  • This is exactly the problem generics are designed to fix. Replace "CLASS" with a generic parameter "Item" everywhere in your function, and the rest should work exactly as you'd like.

    func getMultiple<Item: Decodable>(completion: @escaping(Result<[Item], APIError>) -> Void) {
                    ^^^^^^^^^^^^^^^^^                               ^^^^
    

    For more, see Generic Parameters and Arguments in the Swift Programming Language.

    I often suggest adding an extra parameter to your function signature to make it easier to pass the type (this is similar to how JSONDecoder works):

    func getMultiple<Item: Decodable>(of: Item.Type = Item.self, completion: @escaping(Result<[Item], APIError>) -> Void) {
                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^
    

    This of parameter isn't used anywhere; it's just means you can include the type directly in the call a little more easily:

    .getMultiple(of: User.self) { user in ... }
    

    The addition of = Item.self just means that if the type is known from context, you don't have to pass it, which can be convenient.