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

时间:2014-08-31 01:26:53

标签: arrays swift protocols downcast

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无关。

1 个答案:

答案 0 :(得分:16)

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

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协议。 documentation明确指出:

  

只有在协议标记为@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

在&#34;常规&#34;中写下项目而不是我们得到的预期:

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运算符来检查协议一致性,因此文档仅适用于桥接协议。