强制转换,即使协议需要给定类型

Forced to cast, even if protocol requires given type

我有以下代码:

import UIKit

protocol Fooable: class where Self: UIViewController {
    func foo()
}

class SampleViewController: UIViewController, Fooable {

    func foo() {
        print("foo")
    }
}

let vc1: Fooable = SampleViewController()
let vc2: Fooable = SampleViewController()


// vc1.show(vc2, sender: nil) - error: Value of type 'Fooable' has no member 'show'

// (vc1 as! UIViewController).show(vc2, sender: nil) - error: Cannot convert value of type 'Fooable' to expected argument type 'UIViewController'

(vc1 as! UIViewController).show((vc2 as! UIViewController), sender: nil)

注释行无法编译。

为什么 我被迫将协议类型对象转换为 UIViewController 即使 Fooable 协议要求,符合它的类型继承自 UIViewController?

实例化视图控制器时,您不需要转换为 Fooable 类型的对象。以下作品:

import UIKit

protocol Fooable: class where Self: UIViewController {
    func foo()
}

class SampleViewController: UIViewController, Fooable {

    func foo() {
        print("foo")
    }
}

let vc1 = SampleViewController()
let vc2 = SampleViewController()


vc1.show(vc2, sender: nil)

任何 class 都可以实现此协议,但只有 UIViewController 可以使用 func foo() 方法。

采用协议 Fooable 告诉编译器这个特定的 UIViewController 响应 foo(),不多不少。

反之结论Fooable是否不一定变成UIViewController

如果受影响的 class 不是 UIViewController,则约束 Self: UIViewController 只是编译器在编译时 抱怨 的另一个信息

在您将 SampleViewController 注释为 Fooable 的情况下,编译器只知道 SampleViewController 响应 foo()。它不知道该类型实际上是 UIViewController.

的子class

因此,如果您想访问具体 class 的属性,请不要将具体 class 注释到协议中。

但是您可以将 show 方法和其他常用属性/方法添加到协议中

protocol Fooable: class where Self: UIViewController {
    func foo()
    func show(_ vc: Fooable, sender: Any?)
}

那么可以使用Fooable因为编译器知道采用协议的类型响应方法


例如,当您要创建异构但受限的集合类型时,将类型注释为协议的合适做法是

let array : [CustomStringConvertible] = ["Foo", 1, false]
array.forEach{ print("\([=11=])")}

代码使用所有项目响应的 description 属性 打印三个项目。编译器将这三个项目识别为具有 description 属性 的 类型,而不是 StringIntBool.

更新:

在Swift 5 中实现了对超级class 约束协议的支​​持。

常见的模式是这样做的:

protocol Fooable {
    func foo()
    var viewController: UIViewController
}

class SampleViewController: UIViewController, Fooable {

    func foo() {
        print("foo")
    }

    var viewController: UIViewController { return self }
}

在 Swift 4 中,您可以创建 UIViewController & Fooable 类型的变量。在Swift3中使用上面的技巧。

首先,class 要求在这里是多余的,因为您的协议要求任何 Fooable 扩展 UIViewController,它是 class.

其次,这感觉像是 Swift 团队的某种疏忽,因为即使所有 doStuff 都知道它的参数是它们实现了 Fooable,这仍然有效,这表明您的代码 应该可以工作:

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)

我的推荐? File a bug report.

PS。作为一个额外的 WTF,如果你通过扩展添加对基础 class 的一致性,编译器会崩溃!我的意思是,这样做没有多大意义,但它确实不应该使编译器崩溃。

Swift 5 次更新

在 Swift 5 (Xcode 10.2) 中,您的代码现在可以按预期工作,而无需执行强制转换。


在Swift4.x中,Swift不完全支持对协议的superclass约束,即能够定义protocol P where Self : Cwhere C 是 class.

的类型

正如 Swift 编译器工程师 Slava Pestov 所说:Swift 编译器在实际实现该功能之前不会阻止您执行此操作这一事实是一种疏忽:

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.

然而,这是一个旨在作为 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.

(编辑:Slava 现已在 #17611, #17651, #17816 & #17851 中实现了此功能,因此您将在 Swift 5 中获得它们,可从 Xcode 10.2)

一旦实现,您将能够将此类协议类型视为 class 类型,它需要从中继承符合类型(例如,允许您将 Fooable 视为 UIViewController 而不必转换),就像你可以将 class 存在的 Fooable & UIViewController 视为 UIViewController.

不仅如此,您还可以直接在协议上而不是在 where 子句中声明 superclass 要求,例如:

protocol Fooable : UIViewController {
    func foo()
}

但是,在 Swift 5 之前,我会建议转向 非常清楚 超级class 受限协议——它们目前有一些令人讨厌的粗糙边缘.

例如,这会在 Swift 4.1:

中编译错误并在运行时崩溃
class C : P {
  func speak() {}
}

protocol P where Self : C {
  func speak()
}

let c: P = C()
c.speak()

它会在该语言的更高版本中使编译器崩溃 (SR-6816)。

作为变通方法,您可以使用带有 class 存在类型别名的带下划线的协议来强制执行 class 约束。例如:

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)