问题描述
今天在和 Swift 打交道时,我遇到了一件奇怪的事情.这是我开发的单元测试,它显示了使用 Swift 的 AnyObject 时的一些意外行为.
In messing around with Swift today I came across a strange thing. Here's the unit test I developed which shows some unexpected behaviours when using Swift's 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 函数.
This code is a simplification of some other code where there is a Swift function that can only return a AnyObject
.
正如预期的那样,在创建了 TestClass
的实例后,将其转换为 AnyObject
并设置另一个变量,访问属性 xyz
不会编译,因为 AnyObject
没有这样的属性.
As expected, after creating an instance of TestClass
, casting it to AnyObject
and setting another variable, accessing the property xyz
won't compile because AnyObject
does not have such a property.
但令人惊讶的是,编译器接受了一个名为 name
的属性,因为 NSException
上有一个该名称的属性.Swift 似乎很乐意接受任何属性名称,只要它存在于运行时的某处即可.
But surprisingly a property called name
is accepted by the compiler because there is a property by that name on NSException
. It appears that Swift is quite happy to accept any property name as long as it exists somewhere in the runtime.
下一个意想不到的行为和让这一切开始的事情是尝试访问 name
属性返回 nil.观察调试器中的各种变量,我可以看到 typeAnyObject
指向原始的 TestClass
实例,它的 name
属性的值为名称".
The next unexpected behaviour and the thing that got all this started is that attempting to access the name
property returns a nil. Watching the various variables in the debugger, I can see that typeAnyObject
is pointing at the original TestClass
instance and it's name
property has a value of "name".
Swift 在访问 typeAnyObject.name
时不会抛出错误,所以我希望它找到并返回name".但相反,我得到了零.
Swift doesn't throw an error when accessing typeAnyObject.name
so I would expect it to find and return "name". But instead I get nil.
如果有人能对这里发生的事情有所了解,我会很感兴趣?
I would be interested if anyone can shed some light on what is going on here?
我主要担心的是,我希望 Swift 在访问 AnyObject
上不存在的属性时抛出错误,或者找到并返回正确的值.目前两者都没有发生.
My main concern is that I would expect Swift to either throw an error when accessing a property that does not exist on AnyObject
, or find and return the correct value. Currently neither is happening.
推荐答案
类似于 Objective-C,可以向 id
发送任意消息,可以在 AnyObject
的实例上调用任意属性和方法在斯威夫特.然而,细节有所不同,它记录在与 Objective-C API 交互在在 Cocoa 和 Objective-C 中使用 Swift"一书中.
Similar as in Objective-C, where you can send arbitrary messages to id
,arbitrary properties and methods can be called on an instance of AnyObject
in Swift. The details are different however, and it is documented inInteracting with Objective-C APIsin the "Using Swift with Cocoa and Objective-C" book.
Swift 包含一个表示某种对象的 AnyObject
类型.这类似于 Objective-C 的 id
类型.Swift 将 id
导入为 AnyObject
,这允许您编写类型安全的 Swift 代码,同时保持无类型对象的灵活性.
...
您可以调用任何 Objective-C 方法并访问 AnyObject 值上的任何属性,而无需强制转换为更具体的类类型.这包括使用 @objc
属性标记的 Objective-C 兼容方法和属性.
...
当您对 AnyObject
类型的值调用方法时,该方法调用的行为就像一个隐式解包的可选项.您可以使用与用于协议中的可选方法相同的可选链语法来可选地调用 AnyObject
上的方法.
这是一个例子:
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
如果对象没有那个属性.
obj.timeIntervalSinceReferenceDate
is an implicitly unwrapped optionaland nil
if the object does not have that property.
这里有一个检查和调用方法的例子:
Here an example for checking and calling a method:
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
是一个隐式展开的可选闭包.那个代码可以使用可选链接进行简化:
obj.characterAtIndex
is an implicitly unwrapped optional closure. That codecan be simplified using optional chaining:
func tryToGetFirstCharacter(obj : AnyObject) {
if let c = obj.characterAtIndex?(0) {
print(c)
} else {
print("does not respond to `characterAtIndex`")
}
}
就您而言,TestClass
没有任何 @objc
属性.
In your case, TestClass
does not have any @objc
properties.
let xyz = typeAnyObject.xyz // error: value of type 'AnyObject' has no member 'xyz'
不编译,因为编译器不知道 xyz
属性.
does not compile because the xyz
property is unknown to the compiler.
let name = typeAnyObject.name // String!
编译是因为——正如你注意到的——NSException
有一个 name
属性.然而,该值是 nil
因为 TestClass
没有Objective-C 兼容 name
方法.如上所述,您应该使用可选绑定以安全地解包值(或针对 nil
进行测试).
does compile because – as you noticed – NSException
has a name
property.The value however is nil
because TestClass
does not have anObjective-C compatible name
method. As above, you should use optionalbinding to safely unwrap the value (or test against nil
).
如果你的类是从 NSObject
class TestClass : NSObject {
var name : String?
var xyz : String?
}
然后
let xyz = typeAnyObject.xyz // String?!
编译.(或者,使用 @objc
标记类或属性.)但是现在
does compile. (Alternatively, mark the class or the properties with @objc
.)But now
let name = typeAnyObject.name // error: Ambigous use of `name`
不再编译.原因是 TestClass
和 NSException
有一个 name
属性,但具有不同的类型(String?
vs String
),所以那个表达式的类型是不明确的.这种歧义只能是通过(可选)将 AnyObject
转换回 TestClass
来解决:
does not compile anymore. The reason is that both TestClass
and NSException
have a name
property, but with different types (String?
vs String
),so the type of that expression is ambiguous. This ambiguity can only beresolved by (optionally) casting the AnyObject
back to TestClass
:
if let name = (typeAnyObject as? TestClass)?.name {
print(name)
}
结论:
- 您可以在
AnyObject
的实例上调用任何方法/属性,如果方法/属性与 Objective-C 兼容. - 您必须针对
nil
或使用可选绑定来检查实例是否确实具有方法/属性. - 如果多个类具有(Objective-C)兼容,则会出现歧义名称相同但类型不同的方法.
- You can call any method/property on an instance of
AnyObject
if thatmethod/property is Objective-C compatible. - You have to test the implicitly unwrapped optional against
nil
oruse optional binding to check that the instance actually has thatmethod/property. - Ambiguities arise if more than one class has (Objective-C) compatiblemethods with the same name but different types.
特别是因为最后一点,我会尽量避免这种情况如果可能,机制,并可选择强制转换为已知类(如上一个例子).
In particular because of the last point, I would try to avoid thismechanism if possible, and optionally cast to a known class instead(as in the last example).
这篇关于Swift AnyObject 的奇怪行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!