从任何一个Swift失败演员?协议

时间:2017-02-03 22:03:29

标签: swift casting

仅供参考:此处提出的快速错误:https://bugs.swift.org/browse/SR-3871

我遇到了一个奇怪的问题,即演员阵容无法正常工作,但控制台会将其显示为正确的类型。

我有一个公共协议

public protocol MyProtocol { }

我在一个模块中实现它,使用返回实例的公共方法。

internal struct MyStruct: MyProtocol { }

public func make() -> MyProtocol { return MyStruct() }

然后,在我的视图控制器中,我触发一个带有该对象的segue作为发送者

let myStruct = make()
self.performSegue(withIdentifier: "Bob", sender: myStruct)

到目前为止一切都很好。

问题出在我的prepare(for:sender:)方法中。

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "Bob" {
        if let instance = sender as? MyProtocol {
            print("Yay")
        }
    }
}

但是,实例转换为MyProtocol始终返回nil

当我在控制台中运行po sender as! MyProtocol时,它会向我显示错误Could not cast value of type '_SwiftValue' (0x1107c4c70) to 'MyProtocol' (0x1107c51c8)。但是,po sender将输出有效的Module.MyStruct实例。

为什么这个演员没有工作?

(我已经设法通过在一个结构中装载我的协议来解决它,但我想知道为什么它不能正常工作,以及是否有更好的方法来修复它)

4 个答案:

答案 0 :(得分:21)

这是一个非常奇怪的错误 - 当一个实例通过在_SwiftValue中加框并且静态类型为Any(?)时,它实际上已经被桥接到Obj-C。然后,该实例无法转换为符合它的给定协议。

Joe Groff在bug report you filed的评论中说:

  

这是一般的“运行时动态转换不会桥接,如果有必要桥接到协议”错误的实例。由于发件人被视为_SwiftValue对象类型,并且我们试图找到它不符合的协议,我们放弃而不尝试桥接类型。

更简单的例子是:

protocol P {}
struct S : P {}

let s = S()

let val : Any = s as AnyObject // bridge to Obj-C as a _SwiftValue.

print(val as? P) // nil

奇怪的是,首先转换为AnyObject然后转换为协议似乎有效:

print(val as AnyObject as! P) // S()

所以看起来静态输入AnyObject使得Swift也检查了桥接类型的协议一致性,允许强制转换成功。正如Joe Groff在另一篇评论中所解释的那样,其原因是:

  

运行时遇到了一些错误,它们只尝试将某些转换转换到一个深度级别,但是在执行其他转换之后却没有(因此AnyObject - > bridge - >协议可能有效,但Any - > AnyObject - > bridge - >协议没有)。 应该 无论如何都要工作。

答案 1 :(得分:2)

问题是sender必须通过Objective-C世界,但Objective-C并不知道这个协议/结构关系,因为Swift协议和Swift结构都是不可见的。而不是结构,使用类:

protocol MyProtocol {}
class MyClass: MyProtocol { }
func make() -> MyProtocol { return MyClass() }

现在一切都按预期工作,因为sender可以在Objective-C世界中连贯地生活和呼吸。

答案 2 :(得分:0)

这是我的解决方案。我不想把它变成class(re:this answer),因为我的协议正由多个库实现,他们都必须记住这样做。

我把我的协议装入一个结构中。

public struct BoxedMyProtocol: MyProtocol {
    private let boxed: MyProtocol

    // Just forward methods in MyProtocol onto the boxed value
    public func myProtocolMethod(someInput: String) -> String {
        return self.boxed.myProtocolMethod(someInput)
    }
}

现在,我只是传递BoxedMyProtocol的实例。

答案 3 :(得分:0)

仍未解决。我最喜欢和最简单的解决方法是到目前为止的链投射:

if let instance = sender as AnyObject as? MyProtocol {

}