【问题标题】:Failing cast in Swift from Any? to protocolAny 中的 Swift 演员阵容失败?协议
【发布时间】:2017-02-03 22:03:28
【问题描述】:

仅供参考:这里提出了 Swift 错误: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 实例。

为什么这个演员表不起作用?

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

【问题讨论】:

  • 这里只是一时兴起,但是将这里的内部声明 internal struct MyStruct: MyProtocol { } 更改为 public 有什么改变吗?
  • @Dennis Nope :(

标签: swift casting


【解决方案1】:

这是一个非常奇怪的错误——看起来它发生在实例通过被装箱在_SwiftValue 中并被静态类型为Any(?) 桥接到Obj-C 时。然后,该实例无法转换为它所遵循的给定协议。

bug report you filed 的 cmets 中的 Joe Groff 所说:

这是一般“运行时动态转换在需要桥接到协议时不会桥接”错误的一个实例。由于 sender 被视为_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 -> Protocol 可能有效,但 Any -> AnyObject -> bridge - > 协议没有)。无论如何,它应该工作。

【讨论】:

  • 我已经使用协议 P 扩展了 Array 但这种转换不适用于这种情况
  • 这是一个优秀的超级怪异错误的文章。感谢链式转换提示,我什至不知道这是有效代码!
【解决方案2】:

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

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

现在一切都如您所愿,因为sender 可以在 Objective-C 世界中连贯地生活和呼吸。

【讨论】:

  • 我并不是说您隔离的行为是正确的。该结构被装箱,但你会认为它与协议的关系会继续存在。您可能想使用bugs.swift.org 提交错误。
【解决方案3】:

仍然没有修复。到目前为止,我最喜欢和最简单的解决方法是链式转换:

if let instance = sender as AnyObject as? MyProtocol {

}

【讨论】:

    【解决方案4】:

    这是我的解决方案。我不想把它变成class(回复: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 的实例。

    【讨论】:

      【解决方案5】:

      我知道 swift 5.3 解决了这个问题,但有时你必须支持旧版本的 iOS。

      如果您先将协议转换为 AnyObject,则可以转换为协议。

      if let value = (sender as? AnyObject) as? MyProtocol {
         print("Yay")
      }
      

      【讨论】:

        猜你喜欢
        • 2022-12-18
        • 2015-04-20
        • 1970-01-01
        • 1970-01-01
        • 2020-12-04
        • 1970-01-01
        • 1970-01-01
        • 2014-04-05
        相关资源
        最近更新 更多