Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift: casting Any to array of protocol objects

There is a protocol:

protocol Valuable {
    func value() -> Int
}

and a class which implements the protocol:

class Value: Valuable {
    private let v: Int

    init(value: Int) {
        v = value
    }

    func value() -> Int {
        return v
    }
}

There is an array of Value objects stored in a variable of Any type:

let any: Any = [Value(value: 1), Value(value: 2), Value(value: 3)]

It is possible to cast Any to [Value]:

let arrayOfValue = any as? [Value] // [1, 2, 3]

Why it is not possible to case Any to [Valuable]?

let arrayOfValuable = any as! [Valuable] // compiler error BAD INSTRUCTION
let arrayOfValuable2 = any as? [Valuable] // nil
like image 686
Andrii H. Avatar asked Sep 30 '15 08:09

Andrii H.


3 Answers

Updated: In Swift3 it is entirely possible to cast [Any] to a [Valuable]. The cast will succeed as long as all the elements in the array can be casted; the cast will fail otherwise.

var strings: [Any] = ["cadena"]
var mixed: [Any] = ["cadena", 12]

strings as! [String] // ["cadena"]
mixed as? [String] // nil
mixed as! [String] // Error! Could not cast value...

Previously as of Swift 2: To make a [Valuable] out of an [Any] it must be done manually with functions like map as other answers have explained.

There is currently no covariance nor contravariance with generics in Swift (as of Swift 2). This means that arrays of different types, like [String] or [UIView], cannot be casted into each other, nor their types compared.

[UIView] and [UIButton] bear no hierarchy between each other regardless that UIButton is a subclass of UIView. This is why even if the following returns true:

Valuable.self is Any.Type // true

the following casts yield errors for the same reason:

var anyArray: [Any] = ["cadena"]

anyArray as! [String] // BAD_INSTRUCTION error
"some string" as! Double // BAD_INSTRUCTION error

The two clases bear no relation and the cast is impossible, since the as! is a forced cast it booms the error.

like image 170
Maic López Sáenz Avatar answered Nov 19 '22 11:11

Maic López Sáenz


I do some dig and you have to add @objc attribute as follows

@objc
protocol Valuable {
    func value() -> Int
}

class Value: Valuable {
    private let v: Int

    init(value: Int) {
        v = value
    }

    @objc func value() -> Int {
        return v
    }
}

let any: AnyObject = [Value(value: 1), Value(value: 2), Value(value: 3)]

let arrayOfValueable = any as! [Valuable] // [{v 1}, {v 2}, {v 3}]

For more info and to get anwser for "why?": https://stackoverflow.com/a/25589323/989631

Hope this will help you.

Edit

Plus it works only if you use AnyObject instead of Any :(

like image 26
Błażej Avatar answered Nov 19 '22 11:11

Błażej


It works for me:

let arrayOfValuable = arrayOfValue?.map { $0 as Valuable }

or the same:

let arrayOfValuable2 = (any as? [Value])?.map { $0 as Valuable }

In conclusion, arrayOfValuable should have the type of [Valuable]?

Edit:

Or try this:

let arrayOfAny: [Any] = [Value(value: 1), Value(value: 2), Value(value: 3)]
let arrayOfValuable3 = arrayOfAny.map { $0 as Valuable }

Sure, better way to do that is to declare arrayOfAny as [Valuable], so you'll have no problems afterwards.

like image 2
Beraliv Avatar answered Nov 19 '22 12:11

Beraliv