0

I have a generic protocol I'm using to parse the fetched data:

protocol DataParseDelegate: AnyObject {
    associatedtype FetchResult
    func didFetchData(data: FetchResult?, error: Error?)
}

extension DataParseDelegate {
    func didFetchData(data: FetchResult?, error: Error?) {
        // process data
    }
}

which is within a view controller that resolves to a certain type:

class ParentViewController<T>: UIViewController {}
class ChildViewController: ParentViewController<DataModel>, DataParseDelegate {
    typealias FetchResult = DataModel
    
    override func viewDidLoad() {
        super.viewDidLoad()
        FirebaseService.delegate = self
        FirebaseService.shared.db.downloadData()
    }
    
    func didFetchData(data: DataModel?, error: Error?) {
        // process data
    }
}

I'm using a delegate to pass the fetched data to the view controller:

class FirebaseService {
    static let shared = FirebaseService()
    var db: Firestore! {
        let settings = FirestoreSettings()
        Firestore.firestore().settings = settings
        let db = Firestore.firestore()
        return db
    }
    weak var delegate: DataParseDelegate?
    
    func downloadData() {
        let first = db?.collection("myData")
            .order(by: "date")
            .limit(to: 8)
        
        first?.getDocuments(completion: { [weak self] (snapshot: QuerySnapshot?, error: Error?) in
            // handle errors
            self?.delegate?.didFetchData(data: nil, error: error)
            
            // parse the snapshot into DataModel
            self?.delegate?.didFetchData(data: dataModel, error: nil)
        }
    }
}

I'm getting the following error:

Protocol 'DataParseDelegate' can only be used as a generic constraint because it has Self or associate type requirements pointing to the delegate:

weak var delegate: DataParseDelegate?

I tried using the following instead of associatedtype:

protocol DataParseDelegate: AnyObject {
    func didFetchData<T>(data: [T]?, error: Error?)
}

extension DataParseDelegate {
    func didFetchData<T>(data: [T]?, error: Error?) {
        // process data
    }
}

But, I get an error that says:

Generic parameter 'T' cannot be inferred

in regards to the delegate:

first?.getDocuments(completion: { [weak self] (snapshot: QuerySnapshot?, error: Error?) in
    self?.delegate?.didFetchData(data: nil, error: error) <-----
}
Kevvv
  • 3,655
  • 10
  • 44
  • 90
  • In the line `self?.delegate?.didFetchData(data: dataModel, error: nil)`, where does `dataModel` come from? I don't see it declared anywhere... I might be blind. – Sweeper Jun 28 '21 at 03:43
  • Why are you even using a delegate? completion handler closures are more "Swifty" – Paulw11 Jun 28 '21 at 06:15
  • @Sweeper sorry for the confusion. I've redacted the process of parsing JSON into DataModel. – Kevvv Jun 28 '21 at 10:04
  • @Paulw11 I'm using a delegate because I wanted the separation of concern between the fetch process and the view controller. I've also seen some Apple's examples code do this and seemed like a good practice, but I might have to resort to the completion handler closure as you mentioned. – Kevvv Jun 28 '21 at 10:09
  • Better to use a completion handler here – Rico Crescenzio Jun 28 '21 at 10:50
  • A closure is no more tightly coupled than a delegate. – Paulw11 Jun 28 '21 at 12:08

1 Answers1

1

For protocols that have associated types, the compiler needs to know exactly what type the conforming object is. To make that happen, we can either use a conforming concrete type directly or use the protocol as a generic constraint. The actual types that are being referred to won’t be known unless we provide some additional context. So you can give the FirebaseService a concrete type:

weak var delegate: ChildViewController?

That will work but maybe this is not what you want. Please read this excellent article by John Sundell: Why can’t certain protocols, like Equatable and Hashable, be referenced directly?

Boaz Frenkel
  • 618
  • 5
  • 15
  • 1
    Thank you for this. You not only resolved my issue, but the article was also educational regarding the protocol as a type vs. a constraint. A couple questions remain: 1) I'm still unsure how a protocol can be used as a constraint when `Self` is still unknown, the same reason it can't be used as a type. 2) The article leads me to deduce that maybe a generic protocol can't be used as a delegate, although I'm not certain. – Kevvv Jun 28 '21 at 15:48
  • @Kevvv You can use a Generic Protocol as a delegate but you need to give it a specific implementation so the compiler can know the associatedtype. So I guess this is not really what you want to achieve. I would think of a different way and define exactly what you want to achieve... You can also look at this question that seem relevant: https://stackoverflow.com/questions/50075977/using-delegates-on-generic-protocol – Boaz Frenkel Jun 29 '21 at 12:21