svguerin3
svguerin3

Reputation: 2483

Swift - refactoring common code to a protocol

I have multiple classes that have code that calls a common network class to make a GET api call. Below is an example of one:

public typealias Api1Result = (Result<Api1Model>) -> Void

private var path = "the/path/api1"

public enum Api1ServiceError: String, Error {
    case error = "Sorry, the api1 service returned something different than expected"
}

extension Api1Model {

    public static func getApi1(networkClient: NetworkClient = networkClient, completion: @escaping Api1Result) {
        networkClient.getPath(path) { result in
            switch result {
            case .success(let data):
                do {
                    let api1Model = try JSONDecoder().decode(Api1Model.self, from: data)
                    completion(.success(api1Model))
                } catch {
                    completion(.failure(Api1ServiceError.error))
                }
            case .failure(let error):
                completion(.failure(error))
            }
        }
    }    
}

Here is the Result enum if interested:

public enum Result<Value> {
    case success(Value)
    case failure(Error)
}

There are several other model classes, and the only difference is the actual model class being decoded (Api1Model in this case), as well as the completion typealias (Api1Result). It does the exact same thing across several others, just makes the call to the networkClient.getPath() method, checks for success/failure, and calls the completion closure.

Curious if there are any protocol experts out there who could assist in simplifying this and refactoring so I don't have the same boiler-plate code across multiple classes?

Upvotes: 1

Views: 228

Answers (1)

vadian
vadian

Reputation: 285180

Use a protocol extension (untested)

protocol ApiModel {
    associatedtype ApiType : Decodable = Self

    static var path : String { get }
    static func getApi1(networkClient: NetworkClient, completion: @escaping (Result<ApiType>) -> Void)

}

extension ApiModel where Self : Decodable {   
    static func getApi1(networkClient: NetworkClient, completion: @escaping (Result<ApiType>) -> Void) {
        networkClient.getPath(path) { result in
            switch result {
            case .success(let data):
                do {
                    let api1Model = try JSONDecoder().decode(ApiType.self, from: data)
                    completion(.success(api1Model))
                } catch {
                    completion(.failure(Api1ServiceError.error))
                }
            case .failure(let error):
                completion(.failure(error))
            }
        }
    }
}

Make all your classes conform to ApiModel and add the static path property. The type alias is going to be inferred.

Upvotes: 2

Related Questions