Forced to cast, even if protocol requires given type
Firstly, the class requirement is redundant here as your protocol requires that any Fooable extends UIViewController which is a class.
Secondly, this feels like some sort of oversight on the part of the Swift team because this works even though all doStuff knows about its arguments is that they implement Fooable, suggesting that your code should just work:
class Strawman {
let name: String
public func bar(_ x: Strawman) {
print("\(name) bars \(x.name) from entering.")
}
public init(name: String) {
self.name = name
}
}
protocol Fooable where Self: Strawman {
func foo()
}
class StrawFooable: Strawman, Fooable {
public func foo() { print("Foo!") }
}
let sm1 = StrawFooable(name: "Strawman1")
let sm2 = StrawFooable(name: "Strawman2")
// This will not compile if you define doStuff as
// func doStuff(with x: Fooable, and y: Fooable) {
func doStuff<T: Fooable>(with x: T, and y: T) {
x.bar(y)
x.foo()
y.bar(x)
y.foo()
}
// This will not compile if you annotate sm1 and sm2 as Fooable.
doStuff(with: sm1, and: sm2)
My recommendation? File a bug report.
PS. As a bonus WTF if you add conformance to the base class with an extension the compiler crashes! I mean, there's not much point in doing that, but it really shouldn't be crashing the compiler.
Swift 5 update
In Swift 5 (Xcode 10.2), your code now works as expected without having to perform a force cast.
In Swift 4.x, Swift doesn't fully support superclass constraints on protocols, that is, being able to define protocol P where Self : C
where C
is the type of a class.
The fact that the compiler doesn't prevent you from doing this until the feature is actually implemented was an oversight, as said by Swift compiler engineer Slava Pestov:
Slava Pestov added a comment - 31 May 2018 1:19 PM
[...] "protocol P : Foo where Self : Class" was discovered on accident by users, and it doesn't really work completely. It was an oversight that it wasn't banned.
However this is a feature that is intended to be fully implemented in a future version of the language as a part of SE-0156.
Slava Pestov added a comment - 31 May 2018 1:19 PM
Both are supposed to work, but we haven't fully implemented the proposal yet.
(Edit: Slava has now implemented this in #17611, #17651, #17816 & #17851, so you'll get them in Swift 5, available from Xcode 10.2)
Once implemented, you'll be able to treat such a protocol type as the class type that it requires conforming types to inherit from (e.g allowing you to treat your Fooable
as a UIViewController
without having to cast), in the same way that you can treat a class existential such as Fooable & UIViewController
as a UIViewController
.
Not only that, but you'll also be able to state the superclass requirement directly on the protocol rather than in a where
clause, for example:
protocol Fooable : UIViewController {
func foo()
}
However, until Swift 5, I would recommend steering well clear of superclass constrained protocols – they currently have some nasty rough edges around them.
For example, this will miscompile and crash at runtime in Swift 4.1:
class C : P {
func speak() {}
}
protocol P where Self : C {
func speak()
}
let c: P = C()
c.speak()
and it'll crash the compiler in later versions of the language (SR-6816).
As a workaround, you could use an underscored protocol with a class existential typealias in order to enforce the class constraint instead. For example:
import UIKit
protocol _Fooable : class {
func foo()
}
typealias Fooable = _Fooable & UIViewController
class SampleViewController : Fooable /* implicitly : UIViewController */ {
func foo() {
print("foo")
}
}
// ...
let vc1: Fooable = SampleViewController()
let vc2: Fooable = SampleViewController()
vc1.show(vc2, sender: nil)
Adopting the protocol Fooable
tells the compiler that this particular UIViewController
responds to foo()
, no less no more.
In reverse conclusion Fooable
does not become UIViewController
necessarily.
The constraint Self: UIViewController
is just another information for the compiler to complain at compile time if the affected class is not UIViewController
In your case when annotating SampleViewController
to Fooable
the compiler knows only that SampleViewController
responds to foo()
. It does not know that the type is actually a subclass of UIViewController
.
So do not annotate a concrete class to a protocol if you want to access properties of the concrete class.
However you could add the show
method and other common properties / methods to the protocol
protocol Fooable: class where Self: UIViewController {
func foo()
func show(_ vc: Fooable, sender: Any?)
}
then you can use Fooable
because the compiler knows that the type adopting the protocol responds to the method.
A suitable practice to annotate a type to a protocol is for example when you are going to create a heterogenous but restricted collection type
let array : [CustomStringConvertible] = ["Foo", 1, false]
array.forEach{ print("\($0)")}
The code prints the three items using the description
property which all items respond to. The compiler recognizes the three items as types which have a description
property, not as String
, Int
and Bool
.
Update:
In Swift 5 support of superclass constrained protocols is implemented.
Common pattern is to do it like this:
protocol Fooable {
func foo()
var viewController: UIViewController
}
class SampleViewController: UIViewController, Fooable {
func foo() {
print("foo")
}
var viewController: UIViewController { return self }
}
In Swift 4 you can make vars of type UIViewController & Fooable
. In Swift 3 use the above trick.