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

时间:2017-09-15 09:31:21

标签: 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)

注释行无法编译。

为什么我被迫将协议类型对象强制转换为UIViewController,即使Fooable协议要求,符合它的类型也会继承UIViewController?< / p>

5 个答案:

答案 0 :(得分:9)

采用协议Fooable告诉编译器这个特定的UIViewController会响应foo(),不会再多了。{/ p>

反向结论Fooable 必然会成为UIViewController

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

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

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

但是,您可以将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("\($0)")}

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

<强>更新

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

答案 1 :(得分:8)

Swift 5更新

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

在Swift 4.x中,Swift并不完全支持协议的超类约束,也就是说,能够定义protocol P where Self : C,其中C是类的类型。

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

  

Slava Pestov added a comment - 31 May 2018 1:19 PM

     

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

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

  

Slava Pestov added a comment - 31 May 2018 1:19 PM

     

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

修改:Slava现在已在#17611#17651#17816#17851中实现此功能,因此您可以获得它们在Swift 5中,可从Xcode 10.2获得

一旦实现,您就可以将此类协议类型视为需要符合类型继承的类类型(例如,允许您将Fooable视为UIViewController而不必以某种方式将Fooable & UIViewController作为UIViewController处理。

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

protocol Fooable : UIViewController {
    func foo()
}

然而,在Swift 5之前,我建议指导明确的超类约束协议 - 它们目前在它们周围有一些令人讨厌的粗糙边缘。

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

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

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

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

并且它会在语言的更高版本(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)

答案 2 :(得分:3)

常见模式是这样做的:

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

class SampleViewController: UIViewController, Fooable {

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

    var viewController: UIViewController { return self }
}

在Swift 4中,您可以制作UIViewController & Fooable类型的变量。在Swift 3中使用上述技巧。

答案 3 :(得分:1)

首先,类要求在这里是多余的,因为你的协议要求任何Fooable扩展UIViewController这是一个类。

其次,这感觉就像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,如果您使用扩展名添加基类的一致性,编译器会崩溃!我的意思是,这样做并没有多大意义,但它真的不应该让编译器崩溃。

答案 4 :(得分:-1)

在实例化视图控制器时,您不需要转换为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)

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