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

zgo*_*ski 13 swift swift4

我有以下代码:

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)
Run Code Online (Sandbox Code Playgroud)

注释行不编译.

为什么UIViewController即使Fooable协议需要,我也被迫将协议类型对象强制转换为符合它的类型继承UIViewController

vad*_*ian 9

采用该协议Fooable告诉编译器这个特定的UIViewController响应foo(),不能再多了.

在相反的结论,Fooable没有成为UIViewController必然.

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

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

因此,如果要访问具体类的属性,请不要为协议注释具体类.

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

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

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


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

let array : [CustomStringConvertible] = ["Foo", 1, false]
array.forEach{ print("\($0)")}
Run Code Online (Sandbox Code Playgroud)

代码使用description所有项目响应的属性打印三个项目.编译器可识别的三个项目如其中有一个类型的description财产,还不如String,IntBool.

更新:

在Swift 5中,实现了对超类约束协议的支​​持.


Ham*_*ish 8

在斯威夫特4.x中,斯威夫特并不完全支持超制约的协议,也就是说,可以定义protocol P where Self : C在那里C是一个类的类型.

正如Swift编译工程师Slava Pestov所说,编译器在实际实现该功能之前不会阻止您这样做的事实是疏忽.

Slava Pestov添加了评论 - 2018年5月31日下午1:19

[...]"协议P:Foo,其中Self:Class"是由用户意外发现的,并不完全正常.这是一个疏忽,它没有被禁止.

然而,这是一个功能,旨在作为SE-0156的一部分在该语言的未来版本中完全实现.

Slava Pestov添加了评论 - 2018年5月31日下午1:19

两者都应该有效,但我们尚未完全实施该提案.

(编辑:Slava现在已经在#17611,#17651,#17816#17851中实现了这一点,所以你将在Swift 5中获得它们 - 你可以同时尝试主快照或Xcode 10.2 beta)

一旦实现,您将能够将这样的协议类型视为要求符合类型继承的类类型(例如,允许您将其Fooable视为UIViewController无需转换),就像您可以处理的一样类存在诸如Fooable & UIViewController作为一个UIViewController.

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

protocol Fooable : UIViewController {
    func foo()
}
Run Code Online (Sandbox Code Playgroud)

然而,在Swift 5之前,我建议转向远远超出超类约束协议 - 它们目前在它们周围有一些令人讨厌的粗糙边缘.

例如,这将在运行时在Swift 4.1中错误编译和崩溃:

class C : P {
  func speak() {}
}

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

let c: P = C()
c.speak()
Run Code Online (Sandbox Code Playgroud)

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

作为一种变通方法,您可以使用带有类存在类型的强调协议来强制执行类约束.例如:

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)
Run Code Online (Sandbox Code Playgroud)