问题
https://www.raywenderlich.com/148448/introducing-protocol-oriented-programming
protocol Bird {
var name: String { get }
var canFly: Bool { get }
func doSomething()
}
protocol Flyable {
var airspeedVelocity: Double { get }
}
extension Bird {
// Flyable birds can fly!
var canFly: Bool { return self is Flyable }
func doSomething() {
print("default Bird: \(name)")
}
}
class FlappyBird: Bird, Flyable {
let name: String
let canFly = true
var airspeedVelocity: Double = 5.0
init(name: String) {
self.name = name
}
}
class Penguin: Bird {
let name: String
let canFly = false
init(name: String) {
self.name = name
}
}
class Owl<T> : Bird {
let name: String
let power: T
init(name: String, power: T) {
self.name = name
self.power = power
}
}
extension Bird where Self: FlappyBird {
func doSomething() {
print("FlappyBird: \(name)")
}
}
extension Bird where Self: Owl<String> {
func doSomething() {
print("Owl<String>: \(name)")
}
}
let fb = FlappyBird(name:"PAK")
let penguin = Penguin(name:"Mr. Pickle")
let nightOwl = Owl<String>(name:"Night Owl", power:"Who")
let dayOwl = Owl<Int>(name:"Day Owl", power: 50)
let birds: [Bird] = [fb, penguin, nightOwl, dayOwl]
birdloop: for bird in birds {
bird.doSomething()
}
The output I get is:
FlappyBird: PAK
default Bird: Mr. Pickle
default Bird: Night Owl
default Bird: Day Owl
The first result works as expected since
extension Bird where Self: FlappyBird {
func doSomething() {
print("FlappyBird: \(name)")
}
}
The second result works as expected since it calls the default protocol extension:
extension Bird {
// Flyable birds can fly!
var canFly: Bool { return self is Flyable }
func doSomething() {
print("default Bird: \(name)")
}
}
The third result I would expect to print
Owl<String>: Night Owl
since nightOwl
is of type Owl<String>
. But instead it calls the default protocol extension:
default Bird: Night Owl
Is there some reason why
extension Bird where Self: FlappyBird {
func doSomething() {
print("default Bird: \(name)")
}
}
is called for the FlappyBird
type but
extension Bird where Self: Owl<String> {
func doSomething() {
print("Owl<String>: \(name)")
}
}
is not called for the Owl<String>
type?
回答1:
For the generic type Owl<T>
you are allowed to have the constraint where Self: Owl<String>
, but it will only work in contexts where the concrete type information is available.
To make it clear what is happening, consider this:
let nightOwl = Owl<String>(name: "Night Owl", power: "Who")
nightOwl.doSomething() // prints "Owl<String>: Night Owl"
As opposed to this:
let nightOwl: Bird = Owl<String>(name: "Night Owl", power: "Who")
nightOwl.doSomething() // prints "default Bird: Night Owl"
When Swift creates the protocol witness tables for the types Owl<T>
and FlappyBird
, it has to act differently for each one because Owl
is generic. If it doesn't have the concrete type information, i.e. Owl<String>
at the call site, it must use the default implementation for Owl<T>
. This "loss" of type information is happening when you are inserting the owls into the array of type [Bird]
.
In the case of FlappyBird
, since there is only one possible implementation (since it's not generic), the compiler produces a witness table with the "expected" method reference, which is print("FlappyBird: \(name)")
. Since FlappyBird
is not generic, its witness table doesn't need any reference to the unconstrained default implementation of doSomething()
and can therefore correctly call the the constrained implementation even when the concrete type information is missing.
To make it clear that the compiler "needs" to have the fall back behavior for a generic type, you can remove the Bird
conformance from Owl<T>
and try to rely solely on the constrained default implementation. This will result in a compilation error with an error that is, as usual with Swift, highly misleading.
Value of type 'Owl' has no member 'doSomething'
Basically, it seems the witness table can't be built because it requires the existence of an implementation that will work for all types T
on Owl
.
References
- https://forums.swift.org/t/swifts-method-dispatch/7228
- https://developer.apple.com/videos/play/wwdc2016/416/
回答2:
@AllenHumphreys answer here is a decent explanation on the why part.
As for the fix part, implement doSomething()
for the generic Owl
as:
class Owl<T> : Bird {
//...
func doSomething() {
print("Owl<\(type(of: power))>: \(name)")
}
}
and now you no longer need doSomething()
in extension Bird where Self: Owl<String>
来源:https://stackoverflow.com/questions/49816045/is-there-a-way-to-constrain-self-to-a-generic-type