【问题标题】:Swift why does as? require AnyObject vs Any斯威夫特为什么这样做?需要 AnyObject 与 Any
【发布时间】:2016-03-03 14:53:19
【问题描述】:

我在 as? 处遇到编译器错误。类型“Type”不符合协议“AnyObject”。为什么会这样?需要 AnyObject 吗?

func listForKey<Type>(key: String) -> [Type] {

    guard let fullList = (itemList as NSArray).valueForKey( key ) as? NSArray else {
        return [Type]()
    }

    // Filter out any values not matching the expected type such as if nil was used (value wasn't supplied)!
    let typeOnlyList = fullList.filter( {$0 as? Type != nil} )
    guard let foundList = typeOnlyList as? [Type] else {     // <== at as?, Compiler Error 'Type' does not conform to protocol AnyObject
        return [Type]()
    }

    return foundList
}

如果我将声明更改为以下内容,它将编译:

func listForKey<Type:AnyObject>(key: String) -> [Type] {

但是,它不能与 String 对象一起使用,因为 String 的类型是 any。有什么想法吗?

在阅读 Anton 的评论后,我认为我有一个潜在的解决方案。这解决了编译时错误,但现在我得到一个运行时错误(致命错误:数组无法从 Objective-C 桥接):

extension NSArray {
    public func toSwiftArray<Type>() -> [Type] {
        // Filter out any values not matching the expected type such as nil
        let typeOnlyList : [AnyObject] = self.filter( {$0 is Type} )
        let typeOnlyAnyList : [Any] = typeOnlyList as [Any]                // <== Runtime error EXC_BAD_INSTRUCTION
        guard let foundList : [Type] = typeOnlyAnyList as? [Type] else {
            return [Type]()
        }

        return foundList
    }
}

为什么从 [AnyObject] 转换为 [Any] 会导致运行时错误?我认为 [Any] 是 [AnyObject] 的超集。

【问题讨论】:

  • 尝试使用 is 关键字 - 例如在过滤器中传递的闭包中放入 {$0 is Type} 我也建议对泛型类型使用单个大写字母,因为这是约定
  • 我将它切换到 {$0 is String} 因为它更清楚,但问题出在下一行。它在抱怨 as?在下一行
  • 这不会总是通过,因为 [Type] 不能是 nil,但你说 nil 是一个选项
  • 好问题:底层对象有 swift 选项作为成员。因此,NSArray 中很可能包含 NIL 占位符对象。这些占位符对象无法转换为 Type,因为它们是 nil。所以过滤器确保在最终结果中只包含可以转换为类型的对象。因此允许我们从 NSArray 中返回一个有效的 swift [Type]。
  • 我相信这可能与as? 是动态转换和动态转换只能在类上使用这一事实有关,因为结构不能使用继承(你不能在不同的结构类型之间进行转换)。 AnyObject 是 swift 中类类型的表示。

标签: swift generics casting


【解决方案1】:

这里:

guard let fullList = ... as? NSArray

...您将fullList 变量声明为NSArray

因此typeOnlyList 也是NSArray(这是fullList 过滤的结果)。

NSArray 的元素是 AnyObject 的(从 Objective-C 桥接)。

因此,当您尝试强制转换 typeOnlyList as? [Type] 时,Swift 期望 Type 符合 AnyObject 协议。

【讨论】:

  • 有趣,说得通。但是,为什么当我使用 String(而不是泛型 Type)时它会起作用?快速自动桥接到 NSString 吗?它也适用于其他 Any 类型,例如 Int。我可以指定一些协议来允许它接受可以自动装箱的对象吗?
  • 我通过将 self.filter 结果转换为 [AnyObject] 然后转换为 [Any] 来编译它。但是,我在转换为 [Any] 时遇到运行时错误。非常有趣。
  • 如果您执行guard let foundList = typeOnlyList as? [String],那么foundList 将成为[String],您不能在方法结束时返回[Type]。我认为您的方法是将方法的签名更改为func listForKey&lt;Type where Type: AnyObject&gt;(key: String) -&gt; [Type](注意 where 子句)。
  • 是的,如果我将其限制为仅 AnyObject,它就可以工作,但它不适用于 swift String 对象,因为 swift 中的字符串是值类型。
【解决方案2】:

我想这并不能真正回答最初的问题。但是,这是一种可以完成工作的解决方法。我仍然不明白为什么上面的解决方案不起作用(尤其是从 [AnyObject] 转换为 [Any] 时的致命错误),但我采用了另一种效果很好的方法:

extension NSArray {
    public func toSwiftArray<Type>() -> [Type] {
        var swiftArray = [Type]()

        for value in self {
            if let valueOfType = value as? Type {
                swiftArray.append( valueOfType )
            }
        }

        return swiftArray
    }

    public func toSwiftArray<Type>() -> ([Type], [Any]) {
        var swiftTypeArray = [Type]()
        var unknownTypeArray = [Any]()

        for value in self {
            if let valueOfType = value as? Type {
                swiftTypeArray.append( valueOfType )
            } else {
                unknownTypeArray.append( value )
            }
        }

        return (swiftTypeArray, unknownTypeArray)
    }
}

不知道为什么我不能使用 .filter 来执行此操作,但这是一个非常直接的问题解决方案,并且它还允许返回无法转换的值列表的版本。这是转换为具有完全类型安全性的 NSArray 的非常方便的例程。

【讨论】:

    【解决方案3】:

    如果您想在 Swift 中使用 NSArray,您将只能使用类,因为 NSArray 不能容纳其他任何东西。

    extension NSArray {
        public func toSwiftArray<Type: AnyObject>() -> [Type] {
    
            let typeOnlyList : NSArray = self.filter( {$0 is Type} )
            return typeOnlyList as! Array<Type>
        }
    }
    
    let nsArray: NSArray = [10, 20, "my-text"]
    print("NSArray: \(nsArray)")
    
    let swiftArray: [NSString] = nsArray.toSwiftArray()
    print("Swift array: \(swiftArray)")
    

    简而言之:使用NSString 而不是String。您也可以将[NSString] 转换为[String] 作为第二步。

    【讨论】:

    • 我应该提供更多关于这个问题的背景知识。我在具有动态属性的 @objc 类上使用 Swift 中的键值编码。这些属性是通过 ObjectMapper 从 JSON 设置的。该类具有 String Optionals,因此如果 JSON 未提供它们,则它们为零。然后我使用 (itemList as NSArray).valueForKey(key) 之类的东西来获取与键匹配的所有项目的列表。这些项目都将属于同一类型,除了那些为 nil 的项目。所以我需要一种安全的方法来让它们恢复到像 String 这样的快速类型。
    • @TodCunningham 你能提供一个最小可重复的例子吗? Obj-C 中的演示数据以及您想在 Swift 中对它们做什么?
    • 您的代码确实有效。这很有趣,因为唯一真正的区别是 as!与作为?。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-07-25
    • 2016-09-19
    • 2017-07-22
    • 2017-03-24
    • 1970-01-01
    • 2015-05-12
    • 2016-04-25
    相关资源
    最近更新 更多