当前位置: 首页 > 面试题库 >

Swift的AnyObject的奇怪行为

艾志尚
2023-03-14
问题内容

今天在与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)

    }
}

这段代码简化了一些其他代码,其中只有一个Swift函数只能返回AnyObject

不出所料,在创建的实例之后TestClass,将其强制转换为AnyObject并设置另一个变量,xyz由于AnyObject没有这样的属性,因此无法编译该属性。

但令人惊讶的name是,编译器接受了一个名为的属性,因为on上有该名称的属性NSException。看起来,只要它在运行时的某个地方存在,Swift很乐意接受任何属性名称。

下一个意外行为以及引起所有这些后果的是,尝试访问该name属性将返回nil。观察调试器中的各种变量,我可以看到它typeAnyObject指向原始TestClass实例,并且它的name属性值为“
name”。

Swift在访问时不会引发错误,typeAnyObject.name因此我希望它能够找到并返回“名称”。但是相反,我得到零。

如果有人可以阐明这里发生的事情,我将很感兴趣。

我主要担心的是,我希望Swift在访问上不存在的属性时抛出错误AnyObject,或者找到并返回正确的值。目前都没有发生。


问题答案:

与Objective-C中类似,您可以在其中发送任意消息id,可以AnyObject 在Swift
的实例上调用任意属性和方法。但是,细节有所不同,并且 在“将Swift与Cocoa和Objective-C一起使用”一书中记录了 与Objective-C
API的交互

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),使表达式的类型是不明确的。这种歧义只能通过(可选)将其强制转换AnyObjectTestClass

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

结论:

  • 您可以在实例的任何方法/属性AnyObject是否与Objective-C兼容的情况下调用该方法/属性。
  • 您必须针对隐式展开的可选对象进行测试,nil或使用可选绑定来检查实例是否确实具有该方法/属性。
  • 如果不止一个类具有(Objective-C)兼容的名称相同但类型不同的方法,则会产生歧义。

特别是由于最后一点,如果可能的话,我将尽量避免使用此机制,而是可选地将其强制转换为已知的类(如上例所示)。



 类似资料:
  • 我有以下代码来解析一个JSON文件: 要处理以下JSON文件: 如果我执行此代码,我将收到以下错误: 所以我开始一步一步地调试应用程序,看看part processing()中的哪个代码部分抛出了这个异常。令人惊讶的是,那里的所有代码都正常执行:没有抛出异常,也没有返回结果I except。 更让我惊讶的是,当我稍微改变第一种方法的代码时,它可以在不产生异常的情况下工作。 我不知道println方

  • 问题内容: 我在GregorianCalendar类中遇到一个奇怪的行为,我想知道我是否真的做得不好。 仅当初始化日期的月份的实际Maximum大于我将日历设置为的月份时,才追加此值。 这是示例代码: 我知道问题是由于日历初始化日期是31天(可能是5月),与设置为2月(28天)的月份混淆了。修复很容易(只需在设置年和月之前将day_of_month设置为1),但是我想知道这确实是想要的行为。有什么

  • 问题内容: 我正在为一个问题而苦苦挣扎,我不明白为什么它不起作用。如何通过将变量传递并转换为? 为什么在顶部代码段中不起作用,但在行下方的底部代码段中起作用? 唯一的区别似乎是添加了一个额外的变量,该变量也被键入为? 问题答案: 该是一种原始类型,同时是一个普通的Java类。您不能在原始类型上调用方法。但是该方法在上可用,如javadoc中所示 有关这些原始类型的更多信息,请参见此处

  • 问题内容: 为什么的到哪里去了? 问题答案: 删除任何字符,并从字符串的开头和结尾。

  • 问题内容: 我认为这是一个正常程序,但这是我得到的输出: 有人可以向我解释一下吗? 问题答案: 这是有据可查的PHP行为,请参阅php.net的foreach页面上的警告。 警告 即使在 foreach 循环之后,仍保留 $ value的 引用和最后一个数组元素。建议通过unset()销毁它。 __ 编辑 尝试逐步了解此处实际发生的情况

  • 问题内容: 我有上面的代码,但我不知道为什么会产生 而不是 非常感谢 问题答案: 使用量词来匹配1个或多个空格,而不是:- 表示匹配0个或多个空格,并且将在每个字符之前匹配一个空字符,并由一个空格代替。