Justin Garcia
Justin Garcia

Reputation: 326

reimplementation of a function added in a protocol extension isn’t called

I added a protocol extension for a protocol with an associated type called PickerType. I wrote a reimplementation of a function, refresh(:,completion:) that’s defined in the protocol and implemented in a different protocol extension.

But the function inside my new extension isn’t called when I call refresh(:,completion:) unless the compiler knows what type PickerType is. I wrote the following:

extension PickerItemProvider where PickerType: Equatable & SyncableEntity {
    func refresh(_ sender: Any, completion: (() -> Void)?) {
        print("we're trying to have this implementation called")
        PickerType.startSync()
    }
}

It gets called as I would expect if I call refresh(:,completion:) on a PickerSectionProvider<ObservationType> (see the code from my Playground below) but not when I call refresh(:,completion:) on an ItemProvider, which is a generic type that must conform to PickerItemProvider (again see my code below).

// We are trying to get our pickers to sync their provided type when you pull to refresh. But the implementation of refresh(:, completion:) that we added doesn’t get called.

import Foundation

protocol PickerItemProvider: class {
    associatedtype PickerType
    func findItem(by identifier: NSNumber) -> PickerType?
    func itemAt(_ indexPath: IndexPath) -> PickerType?
    func refresh(_ sender: Any, completion: (() -> Void)?)
}

extension PickerItemProvider {
    func findItem(by identifier: NSNumber) -> PickerType? {
        return nil
    }

    public func refresh(_ sender: Any, completion: (() -> Void)?) {
        print("the default refresh implementation")
    }
}

public class PickerSectionProvider<ProvidedType: Equatable> : PickerItemProvider {
    func itemAt(_ indexPath: IndexPath) -> ProvidedType? {
        return nil
    }
}

extension PickerItemProvider where PickerType: Equatable & SyncableEntity {
    func refresh(_ sender: Any, completion: (() -> Void)?) {
        print("we’re trying to have this implementation called instead of the above implementation of refresh")
        PickerType.startSync()
    }
}

protocol SyncableEntity {
    static func startSync()
}

extension SyncableEntity {
    static func startSync() {

    }
}

class ObservationType: Equatable, SyncableEntity {

}

func ==(lhs: ObservationType, rhs: ObservationType) -> Bool {
    return false
}

class GenericPickerViewController<PickerType: Equatable, ItemProvider: PickerItemProvider> where ItemProvider.PickerType == PickerType {
    var itemProvider: ItemProvider?

    init() {

    }

    func foo() {
        // Why doesn’t the implementation of refresh(:,completion:) we added get called here?
        itemProvider?.refresh("dummy sender") {

        }
    }
}

class PopupPickerRow<T: Equatable, ItemProvider: PickerItemProvider> where ItemProvider.PickerType == T {
    var pickerController = GenericPickerViewController<T, ItemProvider>()
}

let pickerSectionProvider = PickerSectionProvider<ObservationType>()

let row = PopupPickerRow<ObservationType, PickerSectionProvider<ObservationType>>()

row.pickerController.itemProvider = pickerSectionProvider

row.pickerController.foo()

Upvotes: 3

Views: 125

Answers (2)

Justin Garcia
Justin Garcia

Reputation: 326

I added two functions on GenericPickerViewController, one for setting it up with a PickerItemProvider whose PickerType simply conforms to Equatable, and another to set it up with a PickerItemProvider whose PickerType conforms to SyncableEntity. This way the compiler knows which refresh(:,completion:) to call. Here is the Playground code:

import Foundation
import CoreData

protocol PickerItemProvider: class {
    associatedtype PickerType
    func itemAt(_ indexPath: IndexPath) -> PickerType?
}

extension PickerItemProvider {
    public func refresh(_ sender: Any, completion: (() -> Void)?) {
        print("the default refresh implementation")
    }
}

public class PickerSectionProvider<ProvidedType: Equatable> : PickerItemProvider {
    func itemAt(_ indexPath: IndexPath) -> ProvidedType? {
        return nil
    }
}

extension PickerItemProvider where PickerType: Equatable & SyncableEntity {
    func refresh(_ sender: Any, completion: (() -> Void)?) {
        print("we’re trying to have this implementation called instead of the above implementation of refresh")
        PickerType.startSync()
        completion?()
    }
}

protocol SyncableEntity {
    associatedtype EntityType
    static func startSync()
}

extension SyncableEntity {
    static func startSync() {
        print("starting sync")
    }
}

class ObservationType: Equatable, SyncableEntity {
    typealias EntityType = NSManagedObject
}

func ==(lhs: ObservationType, rhs: ObservationType) -> Bool {
    return false
}

class GeneralPickerViewController<PickerType: Equatable, ItemProvider: PickerItemProvider> where ItemProvider.PickerType == PickerType {
    private var itemProvider: ItemProvider?

    private var refresher: ((Any, (() -> ())?) -> ())?

    func setup<T: PickerItemProvider>(with itemProvider: T) where T.PickerType == PickerType {
        refresher = { sender, completion in
            itemProvider.refresh(self, completion: {
                completion?()
            })
        }
        self.itemProvider = (itemProvider as! ItemProvider)
    }

    func setup<T: PickerItemProvider>(with itemProvider: T) where T.PickerType == PickerType, PickerType: SyncableEntity {
        refresher = { sender, completion in
            itemProvider.refresh(self, completion: {
                completion?()
            })
        }
        self.itemProvider = (itemProvider as! ItemProvider)
    }

    func foo() {
        refresher?(self, {
            print("finished")
        })
    }
}

class PopupPickerRow<T: Equatable, ItemProvider: PickerItemProvider> where ItemProvider.PickerType == T {
    var pickerController = GeneralPickerViewController<T, ItemProvider>()
}

let pickerSectionProvider = PickerSectionProvider<ObservationType>()

let row = PopupPickerRow<ObservationType, PickerSectionProvider<ObservationType>>()

row.pickerController.setup(with: pickerSectionProvider)

row.pickerController.foo()

Upvotes: 2

Kamran
Kamran

Reputation: 15258

See the corrected implementation below,

import Foundation

protocol PickerItemProvider: class {
    associatedtype PickerType
    func findItem(by identifier: NSNumber) -> PickerType?
    func itemAt(_ indexPath: IndexPath) -> PickerType?
}

extension PickerItemProvider {
    func findItem(by identifier: NSNumber) -> PickerType? {
        return nil
    }

    func refresh(_ sender: Any, completion: (() -> Void)?) {
        print("the default refresh implementation")
    }
}

public class PickerSectionProvider<ProvidedType: Equatable> : PickerItemProvider {
    func itemAt(_ indexPath: IndexPath) -> ProvidedType? {
        return nil
    }
}

extension PickerItemProvider where PickerType: Equatable & SyncableEntity {

    func refresh(_ sender: Any, completion: (() -> Void)?) {
        print("we’re trying to have this implementation called instead of the above implementation of refresh")
        PickerType.startSync()
    }
}

protocol SyncableEntity {
    static func startSync()
}

extension SyncableEntity {
    static func startSync() {

    }
}

class ObservationType: Equatable, SyncableEntity {

}

func ==(lhs: ObservationType, rhs: ObservationType) -> Bool {
    return false
}

class GenericPickerViewController<PickerType: Equatable & SyncableEntity, ItemProvider: PickerItemProvider> where ItemProvider.PickerType == PickerType {
    var itemProvider: ItemProvider?

    init() {

    }

    func foo() {
        // Why doesn’t the implementation of refresh(:,completion:) we added get called here?
        itemProvider?.refresh("dummy sender") {

        }
    }
}

class PopupPickerRow<T: Equatable & SyncableEntity, ItemProvider: PickerItemProvider> where ItemProvider.PickerType == T {
    var pickerController = GenericPickerViewController<T, ItemProvider>()
}

let pickerSectionProvider = PickerSectionProvider<ObservationType>()

let row = PopupPickerRow<ObservationType, PickerSectionProvider<ObservationType>>()

row.pickerController.itemProvider = pickerSectionProvider

row.pickerController.foo()

First of all, when you want to override a method implementation declared as a requirement in the protocol and provided some default implementation in the protocol extension it will always call the method implemented in the extension discarding the where clause requirements. You can see similar issues in this question and this.

So to make the protocol look for the method fulfilling the constraints in where clause you need to remove the method signature from the protocol and keep it only inside the extension as i did above.

Secondly you were missed the PickerType requirement to be Equatable & SyncableEntity while defining GenericPickerViewController and PopupPickerRow so i updated that also.

Upvotes: 2

Related Questions