Constrained Protocol

If I declare a protocol like this:

protocol ChildViewController where Self: UIViewController {
    func foo()
}

extension ChildViewController {
    func foo() {
        print(self.view) // self is a UIViewController so can access it's interface as normal
    }
    var viewController: UIViewController {
        return self // Don't need to cast it
    }
}

Then why outside of the protocol is a variable of type ChildViewController not also a UIViewController?

eg:

let childViewController: ChildViewController = someChildViewController()
let aViewController: UIViewController = childViewController // doesn't compile, need to cast with as! UIViewController (or use childViewController.viewController)

How can the type information change between the locations in code, surely the compiler is consistence wherever the type is used? It's almost like private inheritance which (as I've previously found a nuisance) swift doesn't have.

It's very simple, actually.™

    let childViewController: ChildViewController = someChildViewController()

The variable childViewController is of any type that conforms to to the ChildViewController protocol. This could be a UIViewController instance or any subclass thereof.

    let aViewController: UIViewController = childViewController // doesn't compile, need to cast with as! UIViewController (or use childViewController.viewController)

Now, the variable aViewController shall be exactly of the type UIViewController, but childViewController can be of type UIViewController or any other subclass. That was exactly the protocol contract! ;-)

Actually, it's even simpler than that. The feature in use here isn't fully implemented. In fact it's barely implemented at all. See SR-6000 & SR-6001.

When this is actually implemented, then not only will every ChildViewController be a UIViewController, but the protocol can also be spelled without the where clause. e.g.

protocol ChildViewController: UIViewControlller {
    func foo()
}

Until then, you need to constrain your types to be ChildViewController & UIViewController if you want to be able to call UIViewController methods on them.

It looks like it's already implemented for Swift 5.0, but it might have been added in early enough for 4.2.

1 Like

Ah ok would've been nice to have a warning about that or something. But seems odd that this has to be specifically implemented rather that it being inherently possible. (seems like the compiler special cases every situation rather than implementing some basic rules, from which more complex behaviour can arise).

Is it possible to find out what tickets are included in which swift releases?

Now, the variable aViewController shall be exactly of the type UIViewController

That is not true. It is quite possible to do:

let aViewController: UIViewController = UINavigationController()

(Liskov substitution principle)

1 Like

On the master branch, "protocol P where Self : ..." now produces an error, instructing you to change it to "protocol P : ...".

The compiler has to be able to perform name lookup into a type before building its generic signature. This means that any refined protocols or a superclass has to appear in the protocol's inheritance clause and not on a "Self" constraint.

In Swift 4.2 we did not allow classes in a protocol's inheritance clause because it wasn't supported, and the fact that you could put constraints on "Self" was an oversight that was not really supposed to work.

No, but you can try out a master development snapshot that has the change in it.

3 Likes