Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift: Cast array of objects to array of sub type

Tags:

swift

Say I have an array of Animals and I'd like to cast it to an array of Cats. Here, Animal is a protocol that Cat adopts. I'd like something like let cats: [Cat] = animals as! [Cat] but this seg faults in compilation (btw I'm on both Linux Swift 3 and Mac Swift 2.2). My workaround is to just create a function that downcasts each item individually and adds it to a new array (see small example below). It produces the desired result, but isn't as clean as I'd like.

My questions are:

  1. is this totally dumb and I'm just missing an easier way to do this?

  2. how can I pass a type as the target parameter in the function below, rather than passing an instance? (e.g. I'd like to pass Cat.self rather than Cat(id:0) but doing so causes an error saying cannot convert Cat.Type to expected argument type Cat)

Here's what I have so far:

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)
like image 633
Philip Avatar asked Jun 12 '16 01:06

Philip


1 Answers

  1. Am I missing an easier way to do this?

You can use map to make the conversion:

let cats: [Cat] = animals.map { $0 as! Cat }
  1. how can I pass a type as the target parameter in the function below, rather than passing an instance?

First, you need to remove the instance parameter:

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
}

Since you cannot specify type parameters explicitly, you need to provide the compiler with some info to deduce the type of U. In this case, all you need to do is to say that you are assigning the result to an array of Cat:

let cats: [Cat] = convertArray(animals)
like image 102
Sergey Kalinichenko Avatar answered Sep 27 '22 22:09

Sergey Kalinichenko