简体   繁体   中英

Generic protocol method swift can't recognise type as type

I have created a generic view controller to pick an item from a list and I want to use a generic delegate method to return the item which has been selected. My setup is as follows:

class ListPickerViewController<T>: UIViewController {
    var delegate: ListPickerViewControllerDelegate?
}

protocol ListPickerViewControllerDelegate {
    func listPickerViewControllerDelegate<T>(_ listPickerViewController: ListPickerViewController<T>, selectedItem: T)
}

I have a list of objects which I will use to populate a ListPickerViewController

class MyClass {
    let a = "sometext"
}

In the class I want to get the selected item from I implement the method and give the selected item delegate a type parameter for the type of objects in the list.

func listPickerViewControllerDelegate<MyClass>(_ listPickerViewController: ListPickerViewController<MyClass>, selectedItem: MyClass) {
    print(selectedItem.a)
}

This doesn't compile however! I get an error 'Value of type MyClass has no member a'. So I tried casting it to the type

func listPickerViewControllerDelegate<MyClass>(_ listPickerViewController: ListPickerViewController<MyClass>, selectedItem: MyClass) {
    let selected = selectedItem as! MyClass
}

This gives 'Force cast of MyClass to same type has no effect'.

For some reason I've found that this works:

typealias MyClassAlias = MyClass 

func listPickerViewControllerDelegate<MyClass>(_ listPickerViewController: ListPickerViewController<MyClass>, selectedItem: MyClass) {
    let selected = selectedItem as MyClassAlias
    print(selected.a) // works
}

Surely there's a better way? Or is this a shortcoming of swift?

In the signature of the delegate method in your protocol implementation MyClass is just a generic parameter, you could have used T , K , etc. It doesn't necessarily have a member named a , hence the first compilation error. In

let selected = selectedItem as! MyClass

it is just force-cast to its own type, which could be pretty much anything. MyClass used in the body of the delegate method is the generic parameter used in the function signature, not the MyClass class that has the a member.

If you use the typealias, then it can be used in the body of the method and it compiles, but as soon as the actual type of the MyClass generic parameter is changed to something other than MyClass class, the cast will fail at runtime.

In general you are likely to need a separate delegate protocol implementation for each type of selected object. I'm assuming that the generic parameter T of the view controller is the selected object type. You also need to make sure that a type-specific view controller uses the right delegate implementation.

In Swift you can use associated types to accomplish this. See https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Generics.html .

The view controller and delegate protocol might look like this:

    class ListPickerViewController<T, D : ListPickerViewControllerDelegate>
    : UIViewController
    where D.T == T
    {
        var delegate: D?
    ......
    }

    protocol ListPickerViewControllerDelegate {
        associatedtype T
        func listPickerViewControllerDelegate<D>(_ controller: 
ListPickerViewController<T, D>, selectedItem: T) where D.T == T
    }

A delegate implementation for use with selected items of type MyClass might look as follows:

    class MyDelegateImpl : ListPickerViewControllerDelegate {
        typealias T = MyClass

        internal func listPickerViewControllerDelegate<D>(_ controller: 
ListPickerViewController<MyClass, D>, selectedItem: MyClass) 
    where D.T == MyClass {
            print("In the delegate method!")
            print("MyClass.a = \(selectedItem.a)")
        }
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM