3

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()
Justin Garcia
  • 328
  • 1
  • 11

2 Answers2

2

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.

Kamran
  • 14,987
  • 4
  • 33
  • 51
  • Thank you for your response, but we are trying to let `PopupPickerRow` and `GenericPickerViewController` work with anything as long as it’s Equatable, not restrict ourselves to `Equatable & SyncableEntity`. If I remove that change you made the problem persists. – Justin Garcia Nov 15 '18 at 23:29
  • 1
    Yes, but then your `protocol` requirement to call that method implementation will break and it will call the default method without considering any constraints. I hope you got the idea what was wrong with your implementation so its up to you how you want to handle it. – Kamran Nov 15 '18 at 23:35
  • I think I’m starting to understand. so at compile time the default implementation of `refresh(:,completion:)` is chosen because all that is known about itemProvider is that it’s of a type that conforms to PickerItemProvider where PickerType is Equatable? – Justin Garcia Nov 15 '18 at 23:44
  • so I added an extension on `GenericPickerViewController` where `PickerType` conforms to `Equatable` and `SyncableEntity` that adds a different implementation of `foo()`. calls to `refresh(:,completion:)` on itemProvider in that extension result in calls to the `refresh(:,completion:)` implementation I added. – Justin Garcia Nov 15 '18 at 23:51
  • Well that works in the Playground but not in my project. I guess that’s because when the real foo() is compiled the compiler doesn’t know what type PickerType is going to be, so it chooses the default one not the one in the extension I added. – Justin Garcia Nov 16 '18 at 00:20
2

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()
Justin Garcia
  • 328
  • 1
  • 11