没有从任何一个敏捷投掷?对协议

时间:2022-09-11 19:13:22

FYI: Swift bug raised here: https://bugs.swift.org/browse/SR-3871

FYI:这里提到的Swift bug: https://bugs.swift.org/browse/SR-3871。


I'm having an odd problem where a cast isn't working, but the console shows it as the correct type.

我遇到了一个奇怪的问题,强制转换不起作用,但是控制台显示它是正确的类型。

I have a public protocol

我有公共礼仪

public protocol MyProtocol { }

And I implement this in a module, with a public method which return an instance.

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

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

Then, in my view controller, I trigger a segue with that object as the sender

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

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

All good so far.

到目前为止一切美好的。

The problem is in my prepare(for:sender:) method.

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

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

However, the cast of instance to MyProtocol always returns nil.

然而,对MyProtocol的实例转换总是返回nil。

When I run po sender as! MyProtocol in the console, it gives me the error Could not cast value of type '_SwiftValue' (0x1107c4c70) to 'MyProtocol' (0x1107c51c8). However, po sender will output a valid Module.MyStruct instance.

当我运行po sender作为!在控制台中的MyProtocol,它给我错误不能将类型'_SwiftValue' (0x1107c4c70)的值转换为'MyProtocol' (0x1107c51c8)。但是,po sender将输出一个有效的模块。MyStruct实例。

Why doesn't this cast work?

为什么这个角色不成功?

(I've managed to solve it by boxing my protocol in a struct, but I'd like to know why it's not working as is, and if there is a better way to fix it)

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

3 个解决方案

#1


14  

This is pretty weird bug – it looks like it happens when an instance has been bridged to Obj-C by being boxed in a _SwiftValue and is statically typed as Any(?). That instance then cannot be cast to a given protocol that it conforms to.

这是一个非常奇怪的错误—看起来它发生在一个实例被作为任何(?)静态类型的_SwiftValue框连接到object - c时。然后,不能将该实例转换为它所遵循的给定协议。

According to Joe Groff in the comments of the bug report you filed:

根据乔·格罗夫在你提交的错误报告的评论:

This is an instance of the general "runtime dynamic casting doesn't bridge if necessary to bridge to a protocol" bug. Since sender is seen as _SwiftValue object type, and we're trying to get to a protocol it doesn't conform to, we give up without also trying the bridged type.

这是一个通用的“运行时动态强制转换在必要时不会桥接到协议”错误的实例。由于sender被视为_SwiftValue对象类型,并且我们正在尝试访问它不符合的协议,所以我们在不尝试桥接类型的情况下放弃了它。

A more minimal example would be:

一个比较简单的例子是:

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

Weirdly enough, first casting to AnyObject and then casting to the protocol appears to work:

奇怪的是,首先对任何对象进行强制转换,然后再对协议进行强制转换,效果似乎不错:

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

So it appears that statically typing it as AnyObject makes Swift also check the bridged type for protocol conformance, allowing the cast to succeed. The reasoning for this, as explained in another comment by Joe Groff, is:

因此,似乎可以将其静态输入为AnyObject,使Swift也可以检查桥接类型是否符合协议,从而允许强制转换成功。正如乔·格罗夫(Joe Groff)在另一篇评论中解释的那样,这样做的理由是:

The runtime has had a number of bugs where it only attempts certain conversions to one level of depth, but not after performing other conversions (so AnyObject -> bridge -> Protocol might work, but Any -> AnyObject -> bridge -> Protocol doesn't). It ought to work, at any rate.

运行时有很多错误,它只尝试将某些转换到某个深度级别,而不执行其他转换(因此AnyObject -> bridge ->协议可能有效,但任何-> AnyObject -> bridge ->协议不能)。无论如何,这应该是可行的。

#2


1  

The problem is that the sender must pass through the Objective-C world, but Objective-C is unaware of this protocol / struct relationship, since both Swift protocols and Swift structs are invisible to it. Instead of a struct, use a class:

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

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

Now everything works as you expect, because the sender can live and breathe coherently in the Objective-C world.

现在一切都如你所愿,因为发送者可以在Objective-C世界中有条理地生活和呼吸。

#3


0  

Here's my solution. I didn't want to just make it into a class (re: this answer) because my protocol is being implemented by multiple libraries and they would all have to remember to do that.

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

I went for boxing my protocol into a struct.

我想把我的协议压缩成一个结构体。

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

Now, I just pass around instances of BoxedMyProtocol.

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

#1


14  

This is pretty weird bug – it looks like it happens when an instance has been bridged to Obj-C by being boxed in a _SwiftValue and is statically typed as Any(?). That instance then cannot be cast to a given protocol that it conforms to.

这是一个非常奇怪的错误—看起来它发生在一个实例被作为任何(?)静态类型的_SwiftValue框连接到object - c时。然后,不能将该实例转换为它所遵循的给定协议。

According to Joe Groff in the comments of the bug report you filed:

根据乔·格罗夫在你提交的错误报告的评论:

This is an instance of the general "runtime dynamic casting doesn't bridge if necessary to bridge to a protocol" bug. Since sender is seen as _SwiftValue object type, and we're trying to get to a protocol it doesn't conform to, we give up without also trying the bridged type.

这是一个通用的“运行时动态强制转换在必要时不会桥接到协议”错误的实例。由于sender被视为_SwiftValue对象类型,并且我们正在尝试访问它不符合的协议,所以我们在不尝试桥接类型的情况下放弃了它。

A more minimal example would be:

一个比较简单的例子是:

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

Weirdly enough, first casting to AnyObject and then casting to the protocol appears to work:

奇怪的是,首先对任何对象进行强制转换,然后再对协议进行强制转换,效果似乎不错:

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

So it appears that statically typing it as AnyObject makes Swift also check the bridged type for protocol conformance, allowing the cast to succeed. The reasoning for this, as explained in another comment by Joe Groff, is:

因此,似乎可以将其静态输入为AnyObject,使Swift也可以检查桥接类型是否符合协议,从而允许强制转换成功。正如乔·格罗夫(Joe Groff)在另一篇评论中解释的那样,这样做的理由是:

The runtime has had a number of bugs where it only attempts certain conversions to one level of depth, but not after performing other conversions (so AnyObject -> bridge -> Protocol might work, but Any -> AnyObject -> bridge -> Protocol doesn't). It ought to work, at any rate.

运行时有很多错误,它只尝试将某些转换到某个深度级别,而不执行其他转换(因此AnyObject -> bridge ->协议可能有效,但任何-> AnyObject -> bridge ->协议不能)。无论如何,这应该是可行的。

#2


1  

The problem is that the sender must pass through the Objective-C world, but Objective-C is unaware of this protocol / struct relationship, since both Swift protocols and Swift structs are invisible to it. Instead of a struct, use a class:

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

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

Now everything works as you expect, because the sender can live and breathe coherently in the Objective-C world.

现在一切都如你所愿,因为发送者可以在Objective-C世界中有条理地生活和呼吸。

#3


0  

Here's my solution. I didn't want to just make it into a class (re: this answer) because my protocol is being implemented by multiple libraries and they would all have to remember to do that.

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

I went for boxing my protocol into a struct.

我想把我的协议压缩成一个结构体。

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

Now, I just pass around instances of BoxedMyProtocol.

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