将对象数组转换为子类型数组:Swift

7
假设我有一个动物数组,我想将其转换为猫的数组。这里,动物是猫采用的协议。我希望像let cats: [Cat] = animals as! [Cat]这样做,但这会在编译时出现段错误(顺便说一下,我同时使用Linux Swift 3和Mac Swift 2.2)。我的解决方法是只需创建一个函数,逐个向下转换每个项目并将其添加到新数组中(请参见下面的小例子)。它产生了期望的结果,但不如我所希望的那样干净。

我的问题是:

  1. 这完全愚蠢吗?我是否错过了更简单的方法?

  2. 如何将类型作为下面函数中的目标参数传递,而不是传递实例?(例如,我想传递Cat.self而不是Cat(id:0),但这样做会导致错误,说无法将Cat.Type转换为预期的参数类型Cat)

以下是我目前的进展:

protocol Animal: CustomStringConvertible 
{
    var species: String {get set}
    var id: Int {get set}
}

extension Animal
{
    var description: String 
    {
        return "\(self.species):\(self.id)"
    }
}

class Cat: Animal 
{
    var species = "felis catus"
    var id: Int

    init(id: Int)
    {
        self.id = id
    }
}

func convertArray<T, U>(_ array: [T], _ target: U) -> [U]
{
    var newArray = [U]()
    for element in array
    {
        guard let newElement = element as? U else
        {
            print("downcast failed!")
            return []
        }

        newArray.append(newElement)
    }

    return newArray
}

let animals: [Animal] = [Cat(id:1),Cat(id:2),Cat(id:3)]
print(animals)
print(animals.dynamicType)

// ERROR: cannot convert value of type '[Animal]' to specified type '[Cat]'
// let cats: [Cat] = animals

// ERROR: seg fault
// let cats: [Cat] = animals as! [Cat]

let cats: [Cat] = convertArray(animals, Cat(id:0))
print(cats)
print(cats.dynamicType)
2个回答

7
  1. 我是否错过了更简单的做法?

您可以使用 map 进行转换:

let cats: [Cat] = animals.map { $0 as! Cat }

如何在下面的函数中将类型作为目标参数传递,而不是传递实例?首先,您需要删除实例参数:
func convertArray<T, U>(array: [T]) -> [U] {
    var newArray = [U]()
    for element in array {
        guard let newElement = element as? U else {
            print("downcast failed!")
            return []
        }
        newArray.append(newElement)
    }
    return newArray
}

由于您无法显式指定类型参数,因此需要提供一些信息给编译器以推断 U 的类型。在这种情况下,您只需要说明您将结果分配给一个 Cat 数组即可:

let cats: [Cat] = convertArray(animals)

谢谢,我会尝试移除U并接受它,如果它对我有用的话。我考虑过使用map,但实际上如果向下转换失败,我会抛出异常。 - Philip
@Philip 关于抛出异常,您可以在 map 中执行相同的操作。使用感叹号的 as! 运算符也会在转换失败时抛出异常。 - Sergey Kalinichenko
哦,我没意识到 map 可以接受一个会抛出异常的闭包。是的,让 as! 抛出异常可能可行,但我想要附加一个自定义消息。 - Philip

7

从Swift 4.1开始,使用compactMap是首选方法,假设您不希望在数组中有任何其他动物(例如狗)时使方法完全失败(并实际崩溃)。

let animals: [Animal] = [Cat(id:1),Dog(id:2),Cat(id:3)]
let cats: [Cat] = animals.compactMap { $0 as? Cat }

compactMap会清除任何nil值,因此你最终会得到以下这样的数组:

[Cat(1), Cat(3)]

作为额外的好处,与使用带有append的for循环相比(因为内存空间没有预先分配,而map会自动分配),您还将获得一些性能提升。

1
for循环不是性能问题所在。问题在于没有预先分配足够的容量。 - Sulthan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接