Swift的AnyObject的奇怪行为

时间:2015-10-28 10:38:46

标签: swift runtime

今天乱搞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上不存在的属性时抛出错误,或者找到并返回正确的值。目前都没有发生。

2 个答案:

答案 0 :(得分:2)

与Objective-C类似,您可以向id发送任意消息, 可以在AnyObject的实例上调用任意属性和方法 在斯威夫特。然而,细节是不同的,并在其中记录 Interacting with Objective-C APIs 在“使用Swift with Cocoa和Objective-C”一书中。

  

Swift包含代表某种对象的AnyObject类型。这类似于Objective-C的id类型。 Swift导入idAnyObject,它允许您编写类型安全的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`

不再编译。原因是TestClassNSException 拥有name属性,但属性不同(String? vs String), 所以表达式的类型是模糊的。这种含糊不清只能是 解决方法(可选)将AnyObject转发回TestClass

if let name = (typeAnyObject as? TestClass)?.name {
    print(name)
}

结论:

  • 如果是AnyObject,您可以调用nil实例上的任何方法/属性 方法/属性与Objective-C兼容。
  • 你必须针对pthread_key或者来测试隐式解包的可选项 使用可选绑定来检查实例是否具有该绑定 方法/属性。
  • 如果多个类具有(Objective-C)兼容性,则会出现歧义 具有相同名称但不同类型的方法。

特别是因为最后一点,我会尽量避免这种情况 机制(如果可能),并且可选地转换为已知类 (如最后一个例子)。

答案 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 //现在不会编译