今天乱搞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
属性的值为“name”。
Swift在访问typeAnyObject.name
时不会抛出错误,所以我希望它能找到并返回“name”。但相反,我没有。
如果有人能够了解这里发生的事情,我会感兴趣吗?
我主要担心的是,我希望Swift在访问AnyObject
上不存在的属性时抛出错误,或者找到并返回正确的值。目前都没有发生。
答案 0 :(得分:2)
与Objective-C类似,您可以向id
发送任意消息,
可以在AnyObject
的实例上调用任意属性和方法
在斯威夫特。然而,细节是不同的,并在其中记录
Interacting with Objective-C APIs
在“使用Swift with Cocoa和Objective-C”一书中。
Swift包含代表某种对象的
AnyObject
类型。这类似于Objective-C的id
类型。 Swift导入id
为AnyObject
,它允许您编写类型安全的Swift代码,同时保持无类型对象的灵活性。
...
您可以调用任何Objective-C方法并访问AnyObject值上的任何属性,而无需转换为更具体的类类型。这包括Objective-C兼容的方法和标有@objc
属性的属性 ...
当您在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
。
这是检查和调用方法的示例:
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
方法。如上所述,您应该使用可选项
绑定以安全地展开值(或对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?
vs String
),
所以表达式的类型是模糊的。这种含糊不清只能是
解决方法(可选)将AnyObject
转发回TestClass
:
if let name = (typeAnyObject as? TestClass)?.name {
print(name)
}
结论:
AnyObject
,您可以调用nil
实例上的任何方法/属性
方法/属性与Objective-C兼容。pthread_key
或者来测试隐式解包的可选项
使用可选绑定来检查实例是否具有该绑定
方法/属性。特别是因为最后一点,我会尽量避免这种情况 机制(如果可能),并且可选地转换为已知类 (如最后一个例子)。
答案 1 :(得分:0)
它与NSException没有任何关系!
来自Apple文档的:
协议AnyObject {...}
所有类隐式符合的协议。
当用作具体类型时,所有已知的@objc方法和属性都可用,分别作为AnyObject的每个实例上的隐式解包 - 可选方法和属性
name是@objc属性,xyz不是。
试试这个: - )
让typeAnyObject = instance as Any
或
@objc class TestClass:NSObject { var name:String? var xyz:String? }
let instance = TestClass()instance.xyz =“xyz”instance.name =“name”
将typeAnyObject = instance作为AnyObject
typeAnyObject.name //现在不会编译