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/prerelease/ios/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
我在操场上尝试过这个代码.由于这是纯粹的swift代码,我认为它与@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协议. documentation明确指出:
You can check for protocol conformance only if your protocol is marked with the @objc attribute
为了验证(不是我不相信文档),我在操场上使用了这个代码:
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运算符来检查协议一致性,因此文档仅适用于桥接协议.