Reputation: 167
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!
Upvotes: 2
Views: 272
Reputation: 299585
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.)
Upvotes: 3
Reputation: 61
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
Upvotes: 0