Unusual Behavior in Method in Extension of UICollectionViewDataSource

I'm trying to extend UICollectionViewDataSource to add a method of my own via a protocol extension, but when I override the default implementation in the class that adopts UICollectionViewDataSource, the protocol extension's implementation is still called. Due to this being for some custom UI, I'm having to call the method using the collection view's dataSource property. A simplified version of what I'm trying to do is like this:


// here's where I'm trying to define a method of my own
extension UICollectionViewDataSource {
    func myMethod() {
        print("myMethod called in default implementation")
    }
}

class ViewController: UIViewController {
    // yes, I'm deliberately defining a collection view property separately since I need multiple collection views in the same view controller and don't want them to use the entire window size
    var collectionView = UICollectionView(frame: CGRect(x: 20, y: 20, width: 20, height: 20))
    
    override func viewDidLoad() {
        collectionView.dataSource = self
        collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "identifier")
    }
}

extension ViewController: UICollectionViewDataSource {
    // these 2 methods are only here so the compiler doesn't complain
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 1
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        return collectionView.dequeueReusableCell(withReuseIdentifier: "identifier", for: indexPath)
    }
    
    // here's where I'm overriding it with the specific implementation
    func myMethod() {
        print("myMethod called in overridden implementation")
    }
}


// then in my custom UI code (like a UICollectionView subclass), something like this would be called, and this would call the default implementation in the protocol extension
dataSource?.myMethod()

Of course, if I'm overriding the default protocol implementation, the expection would be that the overridden implementation is what would get called. Why is this happening, and what can be done to get the desired behavior (if possible)?


Thanks!

Accepted Reply

It’s actually doing the right thing, although the right thing is not necessarily what you really want.


When you declare a new method in a protocol extension, it’s NOT a protocol requirement. (Protocol requirements are those declared in the main protocol definition ONLY.) Therefore it’s not a “customization point”, and your class’s declaration doesn’t override it in any sense.


When you call the method via a variable or property of type UICollectionViewDataSource, such as “dataSource” in line 36, the function that’s called is chosen based on the static type, which is the protocol type, and hence the protocol extension method is called, not your class’s method.


As alternative, you could declare a new protocol derived from UICollectionViewDataSource, then conform your view controller to this new protocol. You will then need to refer to the data source as something like “(dataSource as? MyProtocol)”.


FWIW no one really likes the current behavior, but it’s probably going to be a while before a better solution is designed and implemented in the Swift language.

Replies

It’s actually doing the right thing, although the right thing is not necessarily what you really want.


When you declare a new method in a protocol extension, it’s NOT a protocol requirement. (Protocol requirements are those declared in the main protocol definition ONLY.) Therefore it’s not a “customization point”, and your class’s declaration doesn’t override it in any sense.


When you call the method via a variable or property of type UICollectionViewDataSource, such as “dataSource” in line 36, the function that’s called is chosen based on the static type, which is the protocol type, and hence the protocol extension method is called, not your class’s method.


As alternative, you could declare a new protocol derived from UICollectionViewDataSource, then conform your view controller to this new protocol. You will then need to refer to the data source as something like “(dataSource as? MyProtocol)”.


FWIW no one really likes the current behavior, but it’s probably going to be a while before a better solution is designed and implemented in the Swift language.

Thanks for the detailed description of what's going on - I figured it was a Swift implementation detail. I gave your suggestion with defining a new protocol a try, and it works as I'd like it to.