Swift Generics: Extending a non-generic type with a property of generic type where the generic parameter is the extended type

Viewed 520

Problem

I have a type that takes one generic parameter that is required to inherit from UIView:

class Handler<View: UIView> {
   ...
}

Now, I want write a UIView extension to provide a property that returns Handler and uses Self as the generic parameter, so that in subclasses of UIView I'd always get the handler of type Handler<UIViewSubclass>:

extension UIView {
   var handler: Handler<Self>? { return nil }
}

However this does not compile:

Covariant 'Self' can only appear at the top level of property type

I have also tried to define a protocol HandlerProvider first:

public protocol HandlerProvider {
    associatedtype View: UIView

    var handler: Handler<View>? { get }
}

(so far so good) and then extend UIView with that protocol:

extension UIView: HandlerProvider {
    public typealias View = Self

    public var handler: Handler<View>? { return nil }
}

But that does not compile either:

Covariant 'Self' can only appear as the type of a property, subscript or method result; did you mean 'UIView'?

Question

Is there a way in Swift to use Self as a generic parameter for properties in extension?

1 Answers

Here is possible approach (to think with generics in a bit different direction).

Tested with Xcode 11.4 / swift 5.2

// base handling protocol
protocol Handling {
    associatedtype V: UIView

    var view: V { get }
    init(_ view: V)

    func handle()
}

// extension for base class, will be called by default for any
// UIView instance that does not have explicit extension 
extension Handling where V: UIView {
    func handle() {
        print(">> base: \(self.view)")
    }
}

// extension for specific view (any more you wish)
extension Handling where V: UIImageView {
    func handle() {
        print(">> image: \(self.view)")
    }
}

// concrete implementer
class Handler<V: UIView>: Handling {
    let view: V
    required init(_ view: V) {
        self.view = view
    }
}

// testing function
func fooBar() {
    // handlers created in place of handling where type of
    // handling view is know, so corresponding handle function
    // is used
    Handler(UIView()).handle()
    Handler(UIImageView()).handle()
}

Output:

demo

Related