当前位置: 首页 > 面试题库 >

协议类型数组不能向下转换为具体类型数组

微生智刚
2023-03-14
问题内容
protocol P : class {
    var value:Int {get}
}

class X : P {
    var value = 0

    init(_ value:Int) {
        self.value = value
    }
}

var ps:[P] = [X(1), X(2)]
for p in ps {
    if let x = p as? X {   // works for a single variable
        ...
    }
}

if let xs = ps as? [X] {   // doesn't work for an array (EXC_BAD_ACCESS)
    ...
}

如果P是类而不是协议,则代码可以正常工作。类和协议之间有什么区别?它们都被实现为堆中的指针,不是吗?上面的代码可以成功编译,但是在运行时崩溃。这个EXC_BAD_ACCESS错误是什么意思?

感谢@Antonio,但我仍然不了解此示例代码的工作方式。

let someObjects: [AnyObject] = [
    Movie(name: "2001: A Space Odyssey", director: "Stanley Kubrick"),
    Movie(name: "Moon", director: "Duncan Jones"),
    Movie(name: "Alien", director: "Ridley Scott")
]
for movie in someObjects as [Movie] {
    println("Movie: '\(movie.name)', dir. \(movie.director)")
}

AnyObject是特例吗?

参考:https
:
//developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/TypeCasting.html#//apple_ref/doc/uid/TP40014097-CH22-XID_498

protocol P {

}

@objc class X : P {

}

@objc class Y : X {

}

var xs:[X] = [Y(), Y()]
var ps:[P] = [Y(), Y()]


xs as? [Y]  // works
ps as? [Y]  // EXC_BAD_ACCESS

我在操场上尝试了此代码。由于这是纯快速代码,因此我认为它与@objc无关。


问题答案:

暂时忽略可选绑定并使用直接分配:

let x = ps as [X]

报告以下运行时错误:

fatal error: array element cannot be bridged to Objective-C

这意味着从协议数组到采用者数组的向下转换需要obj-c绑定。可以通过将协议声明为objc兼容来轻松解决此问题:

@objc protocol P : class {
    var value:Int {get}
}

通过简单的更改,代码现在可以正常工作,并且不会引发运行时异常。

现在该 如何 解决,但 为何要 解决这个问题。我还没有答案,但是我会尝试更深入地探讨。

附录:弄清楚“为什么”

我花了一些时间研究这个问题,以下是我所附带的内容。

我们有一个协议和一个采用它的类:

protocol P {}
class X : P {}

我们创建一个P数组:

var array = [P]()

将空数组转换为[X]有效:

array as [X] // 0 elements

如果将元素添加到数组,则会发生运行时错误:

array.append(X())
array as [X] // Execution was interrupted, reason: ...

控制台输出显示:

fatal error: array element cannot be bridged to Objective-C

因此,将协议对象数组强制转换为其采用者数组需要桥接。这证明了@objc解决此问题的理由:

@objc protocol P {}
class X : P {}

var array = [P]()
array.append(X())
array as [X] // [X]

筛选文档,我发现了发生这种情况的原因。

为了执行转换,运行时必须检查是否X符合P协议。该文档明确指出:

仅当您的协议标记有@objc属性时,才能检查协议一致性

为了验证(不是我不信任该文档),我在操场上使用了以下代码:

protocol P {}
class X : P {}

let x = X()
let y = x is P

但我得到了另一个错误,指出:

Playground execution failed: <EXPR>:18:11: error: 'is' test is always true 
let y = x is P

相反,在“常规”项目中编写该代码,我们可以得到预期的结果:

protocol P {}
class X {}

func test() {
    let x = X()
    let y = x is P
}

Cannot downcast from 'X' to non-@objc protocol type 'P'

结论:为了将协议类型的数组下转换为具体类型的数组,必须使用该@objc属性标记该协议。原因是运行时使用is操作员来检查协议一致性,因此相应于文档的内容仅适用于桥接协议



 类似资料:
  • 我正在创建一个由数组支持的泛型类型堆栈。当我尝试创建泛型类型数组时,Java不允许我这样做。有人告诉我,我必须创建一个类型为Object的数组,并将其转换为泛型类型。我已经将对象数组转换为类型,但如何处理Java不断给我的未检查类型错误? 这就是我目前所处的位置。 更新:我正在创建一个对象数组,然后在方法的末尾将返回类型转换为T类型。

  • 我试图编译一个从SourceForge得到的仍在开发中的项目。这是它的地址:。https://sourceforge.net/p/groove/code/5475/tree/groove/trunk/我知道这个项目经过了很好的验证,它没有任何错误,但是当我要编译它的时候,我遇到了这个编译错误: 错误在这个文件的第370行:https://sourceforge.net/p/groove/code/

  • 我为协变返回类型的继承创建了一个小示例。基本上有三种不同的类别: 主应用程序: BaseManager: 鸟经理: 当我重写方法以返回时,为什么我需要将类型转换为? 我使用过的重写方法在返回类型上会有所不同吗?作为参考。 编辑: 我有不同的子模型,它们都继承自。所有模型都允许存在一次。我尝试将这些模型添加到列表中,而不是对每个模型使用单例。使用,我想得到实际的模型。也许我得考虑太多了。

  • 此代码有效。它以“未检查或不安全操作”警告进行编译和运行。 当这两个给我运行时错误时 我遇到的错误如下:

  • Clang和GCC都抱怨格式不正确,并给出了奇怪的诊断。 Clang报告 GCC报告 但是,根据expr.static.cast#4 如果存在从E到T的隐式转换序列([over.best.ics]),则表达式E可以显式转换为类型T 将一个类型转换为同一类型不是叫做标识转换吗? over.best.ics#General-8 如果不需要转换来将参数与参数类型匹配,则隐式转换序列是由标识转换([ove

  • 我的一个数据帧(spark.sql)有这个模式。 我需要将其保存到CSV文件,但不使用任何扁平化,以以下格式分解。 我直接使用了命令 ,这符合我的目的,但我需要一个更好的方法。我正在使用派斯帕克