【问题标题】:Is there a way to constrain `Self` to a generic type?有没有办法将 `Self` 限制为泛型类型?
【发布时间】:2018-04-13 11:33:50
【问题描述】:

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()
    }

我得到的输出是:

FlappyBird: PAK
default Bird: Mr. Pickle
default Bird: Night Owl
default Bird: Day Owl

第一个结果按预期工作,因为

extension Bird where Self: FlappyBird {
    func doSomething() {
        print("FlappyBird: \(name)")
    }
}

第二个结果按预期工作,因为它调用了默认协议扩展:

extension Bird {
        // Flyable birds can fly!
        var canFly: Bool { return self is Flyable }
        func doSomething() {
            print("default Bird: \(name)")
        }
    }

我希望打印的第三个结果

Owl<String>: Night Owl

因为nightOwlOwl&lt;String&gt; 类型。但它调用了默认的协议扩展:

default Bird: Night Owl

有什么原因吗

extension Bird where Self: FlappyBird {
  func doSomething() {
    print("default Bird: \(name)")
  }
}

FlappyBird 类型调用,但是

extension Bird where Self: Owl<String> {
  func doSomething() {
    print("Owl<String>: \(name)")
  }
}

Owl&lt;String&gt; 类型是否调用?

【问题讨论】:

    标签: swift generics protocols extension-methods


    【解决方案1】:

    对于泛型类型Owl&lt;T&gt;,您可以拥有约束where Self: Owl&lt;String&gt;,但它仅适用于具体类型信息可用的上下文。

    为了弄清楚发生了什么,请考虑一下:

    let nightOwl = Owl<String>(name: "Night Owl", power: "Who")
    nightOwl.doSomething() // prints "Owl<String>: Night Owl"
    

    与此相反:

    let nightOwl: Bird = Owl<String>(name: "Night Owl", power: "Who")
    nightOwl.doSomething() // prints "default Bird: Night Owl"
    

    当 Swift 为 Owl&lt;T&gt;FlappyBird 类型创建协议见证表时,它必须为每个类型采取不同的行为,因为 Owl 是通用的。如果它没有具体的类型信息,即调用站点的Owl&lt;String&gt;,它必须使用Owl&lt;T&gt; 的默认实现。当您将猫头鹰插入[Bird] 类型的数组时,就会发生这种类型信息的“丢失”。

    FlappyBird 的情况下,由于只有一种可能的实现(因为它不是通用的),因此编译器会生成一个带有“预期”方法引用的见证表,即print("FlappyBird: \(name)")。由于FlappyBird 不是通用的,它的见证表不需要对doSomething() 的无约束默认实现的任何引用,因此即使在缺少具体类型信息的情况下也可以正确调用受限实现。

    为了明确编译器“需要”具有泛型类型的回退行为,您可以从 Owl&lt;T&gt; 中删除 Bird 一致性并尝试仅依赖受约束的默认实现。这将导致编译错误,该错误与 Swift 一样具有高度误导性。

    “猫头鹰”类型的值没有成员“doSomething”

    基本上,似乎无法构建见证表,因为它需要存在适用于 Owl 上的所有类型 T 的实现。

    参考文献

    1. https://forums.swift.org/t/swifts-method-dispatch/7228
    2. https://developer.apple.com/videos/play/wwdc2016/416/

    【讨论】:

      【解决方案2】:

      @AllenHumphreys answer here 是对原因部分的一个很好的解释。

      至于修复部分,将doSomething() 实现为通用Owl 为:

      class Owl<T> : Bird {
          //...
          func doSomething() {
              print("Owl<\(type(of: power))>: \(name)")
          }
      }
      

      现在你在extension Bird where Self: Owl&lt;String&gt; 中不再需要doSomething()

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-08-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-12-20
        相关资源
        最近更新 更多