今天在使用 Swift 时,我遇到了一件奇怪的事情。这是我开发的单元测试,它显示了使用 Swift 的 AnyObject 时的一些意外行为。
class SwiftLanguageTests: XCTestCase {
class TestClass {
var name:String?
var xyz:String?
}
func testAccessingPropertiesOfAnyObjectInstancesReturnsNils() {
let instance = TestClass()
instance.xyz = "xyz"
instance.name = "name"
let typeAnyObject = instance as AnyObject
// Correct: Won't compile because 'xyz' is an unknown property in any class.
XCTAssertEqual("xyz", typeAnyObject.xyz)
// Unexpected: Will compile because 'name' is a property of NSException
// Strange: But returns a nil when accessed.
XCTAssertEqual("name", typeAnyObject.name)
}
}
此代码是其他一些代码的简化,其中有一个只能返回 AnyObject
的 Swift 函数。
正如预期的那样,在创建 TestClass
实例、将其转换为 AnyObject
并设置另一个变量后,将无法访问属性 xyz
编译,因为 AnyObject
没有这样的属性。
但令人惊讶的是,编译器接受了名为 name
的属性,因为 NSException
上有一个名为该名称的属性。看来 Swift 很乐意接受任何属性名称,只要它存在于运行时的某个地方。
下一个意外行为以及引发这一切的原因是尝试访问 name
属性返回 nil。观察调试器中的各种变量,我可以看到 typeAnyObject
指向原始 TestClass
实例,并且它的 name
属性的值为“姓名”。
访问 typeAnyObject.name
时,Swift 不会抛出错误,因此我希望它能够找到并返回“name”。但我却得到了零。
如果有人能解释一下这里发生的事情,我会很感兴趣?
我主要担心的是,我希望 Swift 在访问 AnyObject
上不存在的属性时抛出错误,或者找到并返回正确的值。目前这两种情况都没有发生。
最佳答案
与 Objective-C 类似,您可以向 id
发送任意消息,
可以在 AnyObject
的实例上调用任意属性和方法
在 swift .但是细节有所不同,并且记录在
Interacting with Objective-C APIs
在《将 Swift 与 Cocoa 和 Objective-C 结合使用》一书中。
Swift includes an
AnyObject
type that represents some kind of object. This is similar to Objective-C’sid
type. Swift importsid
asAnyObject
, which allows you to write type-safe Swift code while maintaining the flexibility of an untyped object.
...
You can call any Objective-C method and access any property on an AnyObject value without casting to a more specific class type. This includes Objective-C compatible methods and properties marked with the@objc
attribute.
...
When you call a method on a value ofAnyObject
type, that method call behaves like an implicitly unwrapped optional. You can use the same optional chaining syntax you would use for optional methods in protocols to optionally invoke a method onAnyObject
.
这是一个例子:
func tryToGetTimeInterval(obj : AnyObject) {
let ti = obj.timeIntervalSinceReferenceDate // NSTimeInterval!
if let theTi = ti {
print(theTi)
} else {
print("does not respond to `timeIntervalSinceReferenceDate`")
}
}
tryToGetTimeInterval(NSDate(timeIntervalSinceReferenceDate: 1234))
// 1234.0
tryToGetTimeInterval(NSString(string: "abc"))
// does not respond to `timeIntervalSinceReferenceDate`
obj.timeIntervalSinceReferenceDate
是隐式解包的可选
如果对象没有该属性,则为 nil
。
这里是检查和调用方法的示例:
func tryToGetFirstCharacter(obj : AnyObject) {
let fc = obj.characterAtIndex // ((Int) -> unichar)!
if let theFc = fc {
print(theFc(0))
} else {
print("does not respond to `characterAtIndex`")
}
}
tryToGetFirstCharacter(NSDate(timeIntervalSinceReferenceDate: 1234))
// does not respond to `characterAtIndex`
tryToGetFirstCharacter(NSString(string: "abc"))
// 97
obj.characterAtIndex
是一个隐式展开的可选闭包。那个代码
可以使用可选链来简化:
func tryToGetFirstCharacter(obj : AnyObject) {
if let c = obj.characterAtIndex?(0) {
print(c)
} else {
print("does not respond to `characterAtIndex`")
}
}
就您而言,TestClass
没有任何 @objc
属性。
let xyz = typeAnyObject.xyz // error: value of type 'AnyObject' has no member 'xyz'
无法编译,因为编译器不知道 xyz
属性。
let name = typeAnyObject.name // String!
确实可以编译,因为 - 正如您所注意到的 - NSException
有一个 name
属性。
然而,该值是 nil
因为 TestClass
没有
兼容 Objective-C name
方法。如上所述,您应该使用可选的
绑定(bind)以安全地解开该值(或针对 nil
进行测试)。
如果您的类派生自 NSObject
class TestClass : NSObject {
var name : String?
var xyz : String?
}
然后
let xyz = typeAnyObject.xyz // String?!
确实可以编译。 (或者,使用 @objc
标记类或属性。)
但现在
let name = typeAnyObject.name // error: Ambigous use of `name`
不再编译。原因是 TestClass
和 NSException
有一个 name
属性,但具有不同的类型(String?
与 String
),
所以该表达式的类型是不明确的。这种暧昧只能是
通过(可选)将 AnyObject
强制转换回 TestClass
来解决:
if let name = (typeAnyObject as? TestClass)?.name {
print(name)
}
结论:
- 您可以在
AnyObject
实例上调用任何方法/属性,前提是 方法/属性与 Objective-C 兼容。 - 您必须针对
nil
测试隐式展开的 optional ,或者 使用可选绑定(bind)来检查实例是否确实具有该绑定(bind) 方法/属性。 - 如果多个类具有(Objective-C)兼容,就会出现歧义 具有相同名称但类型不同的方法。
特别是由于最后一点,我会尽力避免这种情况 如果可能的话,机制,并且可以选择转换为已知类 (如上一个示例)。
关于swift - Swift 的 AnyObject 的奇怪行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33388830/