Ken*_*ang 15 arrays protocols downcast swift
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)
...
}
Run Code Online (Sandbox Code Playgroud)
如果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)")
}
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
我在游乐场尝试了这段代码.由于这是纯粹的快速代码,我认为它与@objc无关.
Ant*_*nio 16
暂时忽略可选绑定并使用直接赋值:
let x = ps as [X]
Run Code Online (Sandbox Code Playgroud)
报告以下运行时错误:
fatal error: array element cannot be bridged to Objective-C
Run Code Online (Sandbox Code Playgroud)
这意味着从协议数组到采用者数组的向下转换需要obj-c绑定.通过将协议声明为objc兼容,可以轻松解决这个问题:
@objc protocol P : class {
var value:Int {get}
}
Run Code Online (Sandbox Code Playgroud)
通过这种简单的更改,代码现在可以运行,并且不会引发运行时异常.
现在如何解决,但留下一个开放问题的原因.我还没有答案,但我会尝试深入研究.
附录:弄清楚"为什么"
我花了一些时间来研究这个问题,接下来是我带来的.
我们有一个协议和一个采用它的类:
protocol P {}
class X : P {}
Run Code Online (Sandbox Code Playgroud)
我们创建一个P数组:
var array = [P]()
Run Code Online (Sandbox Code Playgroud)
将空数组转换为[X]
工作:
array as [X] // 0 elements
Run Code Online (Sandbox Code Playgroud)
如果我们向数组添加元素,则会发生运行时错误:
array.append(X())
array as [X] // Execution was interrupted, reason: ...
Run Code Online (Sandbox Code Playgroud)
控制台输出说:
fatal error: array element cannot be bridged to Objective-C
Run Code Online (Sandbox Code Playgroud)
因此,将一组协议对象转换为其采用者的数组需要桥接.这证明了为什么@objc解决了这个问题:
@objc protocol P {}
class X : P {}
var array = [P]()
array.append(X())
array as [X] // [X]
Run Code Online (Sandbox Code Playgroud)
筛选文档,我发现了发生这种情况的原因.
为了执行强制转换,运行时必须检查是否X
符合P
协议.该文件明确指出:
仅当协议标有@objc属性时,才能检查协议一致性
为了验证(不是我不相信文档),我在操场上使用了这段代码:
protocol P {}
class X : P {}
let x = X()
let y = x is P
Run Code Online (Sandbox Code Playgroud)
但我得到一个不同的错误,说明:
Playground execution failed: <EXPR>:18:11: error: 'is' test is always true
let y = x is P
Run Code Online (Sandbox Code Playgroud)
在"常规"项目中写下我们得到的预期:
protocol P {}
class X {}
func test() {
let x = X()
let y = x is P
}
Cannot downcast from 'X' to non-@objc protocol type 'P'
Run Code Online (Sandbox Code Playgroud)
结论:为了将协议类型数组向下转换为具体类型数组,必须使用该@objc
属性标记协议.原因是运行时使用is
运算符来检查协议一致性,因此文档仅适用于桥接协议.