I would like to create a generic protocol for a data fetching service like this:

protocol FetchDataDelegate: AnyObject {

    associatedtype ResultData

    func didStartFetchingData()
    func didFinishFetchingData(with data: ResultData)
    func didFinishFetchingData(with error: Error)
}

class Controller: UIViewController, FetchDataDelegate {

    func didStartFetchingData() {
        //...
    }

    func didFinishFetchingData(with data: ResultData) {
        //...
    }

    func didFinishFetchingData(with error: Error) {
        //...
    }
}

class NetworkManager {

    weak var delegate: FetchDataDelegate?
    //...
}

The Controller class would have a reference to a NetworkManager instance, and through this, I would like to start the network operations. When the operation is ended I would like to call the appropriate delegate function to pass the result back to the controller. But with this setup I got the following error: Protocol 'FetchDataDelegate' can only be used as a generic constraint because it has Self or associated type requirements The question is what should I do to use this generic protocol as a variable type? Or if is not possible what would be the correct way? Thanks!

2 Answers

2
Rob Napier On Best Solutions

While this is closely related to the question David Smith linked (and you should read that as well), it's worth answering separately because it's a different concrete use case, and so we can talk about it.

First, imagine you could store this variable. What would you do with it? What function in NetworkManager could call delegate.didFinishFetchingData? How would you generate the ResultData when you don't know what it is?

The point is that this isn't what PATs (protocols with associated types) are for. It's not their goal. Their goal is to help you add extensions to other types, or to restrict which kinds of types can be passed to generic algorithms. For those purposes, they're incredibly powerful. But what you want are generics, not protocols.

Instead of creating delegates, you should use generic functions to handle the result of a specific call, rather than trying to nail down each view controller to a specific result type (which isn't very flexible anyway). For example, in the simplest, and least flexible way that still gives you progress reporting:

struct APIClient {
    func fetch<Model: Decodable>(_: Model.Type,
                                 with urlRequest: URLRequest,
                                 completion: @escaping (Result<Model, Error>) -> Void)
    -> Progress {

        let session = URLSession.shared

        let task = session.dataTask(with: urlRequest) { (data, _, error) in
            if let error = error {
                completion(.failure(error))
            }
            else if let data = data {
                let decoder = JSONDecoder()
                completion(Result {
                    try decoder.decode(Model.self, from: data)
                })
            }
        }
        task.resume()
        return task.progress
    }
}

let progress = APIClient().fetch(User.self, with: urlRequest) { user in ... }

This structure is the basic approach to this entire class of problem. It can be made much, much more flexible depending on your specific needs.

(If your didStartFetchingData method is very important, in ways that Progress doesn't solve, leave a comment and I'll show how to implement that kind of thing. It's not difficult, but this answer is pretty long already.)

0
David Smith On

Possible duplicate of this

In summary, you cannot use generic protocols as variable types, it would have to be leveraged as a generic constraint since you wouldn't know the type of ResultData at compilation time