Swift Generics and Protocols not working on UIKit

2019-02-26 00:46发布

问题:

TL;DR -> scroll to bottom

In trying to tag Apple on the Protocol-Oriented Programming with Swift, I stumbled upon the following problem while trying to implement a delegation pattern between classes.

I'll start with this example:

protocol PhotoHandlerParent {}

class UIViewController {}

class MyViewController: UIViewController, PhotoHandlerParent {}

class PhotoHandler: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    weak var delegate: PhotoHandlerParent
}

So far, so good. An instance of MyViewController would be happily assigned as PhotoHandler's delegate. But say I not only wanted the delegate object to conform to PhotoHandlerParent, but also be of class UIViewController. In this particular example, so that the PhotoHandler could present and dismiss an UIImagePickerController on behalf of its parent view controller. Much like:

protocol PhotoHandlerParent {}

class UIViewController {}

class MyViewController: UIViewController, PhotoHandlerParent {}

class PhotoHandler: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    weak var delegate: UIViewController, PhotoHandlerParent
}

Unfortunetely, the code above doesn't work on Swift. On the other hand though, Swift does have Generics, that may help in this case. Thus one might try:

protocol PhotoHandlerParent {}

class UIViewController {}

class MyViewController: UIViewController, PhotoHandlerParent {}

class PhotoHandler<Parent where Parent: UIViewController, Parent: PhotoHandlerParent>: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    weak var delegate: Parent
}

Now interestingly enough, an instance of MyViewController would go back to be happily assigned as PhotoHandler's delegate. No compile error, no runtime error. But...

TL;DR: The problem

Running the sample code for this question, it's possible to see that an instance of a class declared with Generics and set as a UIImagePickerController's delegate is never called by it. An instance of an object declared without generics is called by the UIImagePickerController.

My best assumption is that the compiler doesn't complain because it can verify that PhotoHandler conforms to UIImagePickerControllerDelegate. However, at runtime, the PhotoHandler instance is actually a PhotoHandler<MyViewController> instance, thus somehow interfering with the UIImagePickerController's ability to identify that its delegate actually implements its protocol.

Or am I missing something maybe?

Cheers

回答1:

That's correct behavior from the documentation point of view, because:

Method in a generic class cannot be represented in Objective-C


Reply to @Bell App Lab comment:

Open this page and scroll down to Lightweight Generics. Here's note:

Aside from than these Foundation collection classes, Objective-C lightweight generics are ignored by Swift. Any other types using lightweight generics are imported into Swift as if they were unparameterized.

It basically says that generics (ObjC -> Swift) are imported only for Foundation collection classes and rest is ignored - IOW imported as if they are unparameterized.

Maybe we can expect some improvement in this area in the future, but I doubt it.