Search code examples
iosswiftxcodeswift-protocolsassociated-types

Swift5. Protocol extension raises compilation error 'Cannot invoke function with an argument list of type Self'


I'd like to implement some kind of Decorator pattern, which allows to write reusable decorators

So I defined 2 protocols. The first one defines a type for a decorator:

    protocol ViewDecorator {
        associatedtype View: Decoratable
        func decorate(view: View)
    }

    // this is how I expect to use decorator
    class GreenViewDecorator: ViewDecorator {
        typealias View = GreenView

        func decorate(view: GreenView) {
            view.backgroundColor = UIColor.green
        }
    }

The second one defines a type which decoratable view should conform to.

    protocol Decoratable {
        func decorate<T: ViewDecorator>(with decorator: T)
    }

    extension Decoratable where Self: UIView {
        func decorate<T : ViewDecorator>(with decorator: T) {
            decorator.decorate(view: self)
        }
    }

    // exampled of 'decoratable' view
    class GreenView: UIView, Decoratable { }

I defined default implementation of function func decorate<T : ViewDecorator>(with decorator: T) in the protocol extension. I see it's useful if my view will implement the method by default. I just need it only inherit Decoratable protocol. And then I can use it like:

    // example of using decorator with view
    let decorator = GreenViewDecorator()
    greenView.decorate(with: decorator)

But Swift5 compiler raises error at the line decorator.decorate(view: self)

Cannot invoke 'decorate' with an argument list of type '(view: Self)'

Compilation error

============== TOTAL LISTING ==========
    protocol ViewDecorator {
        associatedtype View: Decoratable
        func decorate(view: View)
    }

    protocol Decoratable {
        func decorate<T: ViewDecorator>(with decorator: T)
    }

    extension Decoratable where Self: UIView {
        func decorate<T : ViewDecorator>(with decorator: T) {
            decorator.decorate(view: self)
        }
    }

    class GreenView: UIView, Decoratable { }

    class GreenViewDecorator: ViewDecorator {
        typealias View = GreenView

        func decorate(view: GreenView) {
            view.backgroundColor = UIColor.green
        }
    }

    class ViewController: UIViewController {
        @IBOutlet var greenView: GreenView!

        override func viewDidLoad() {
            super.viewDidLoad()
            let decorator = GreenViewDecorator()
            greenView.decorate(with: decorator)
        }
    }

Solution

  • The type of the argument is the associated type View, but it has not been established anywhere that Self is that type, hence you cannot pass an argument of type Self without establishing that it is compatible. For example:

    extension Decoratable where Self: UIView {
        func decorate<T: ViewDecorator>(with decorator: T) where T.View == Self {
            decorator.decorate(view: self)
        }
    }
    

    (However, if you do this, you will have trouble conforming to the Decoratable protocol because it requires this method for any ViewDecorator. You can change the protocol to also have the same T.View == Self limitation.)