Marcin Kapusta
Marcin Kapusta

Reputation: 5376

Swift generic problem with array and individual item generic support

I have problem with declaring an Array and initialize it with many different generic items and in the same time use those items individually with generic support. Lets look at this:

protocol Item {
    associatedtype ValueType: Any
    var value: ValueType? { get set }
}
class ItemClass<T: Any>: Item {
    typealias ValueType = T
    var value: ValueType?
}

let intItem = ItemClass<Int>()
let stringItem = ItemClass<String>()
let array: [ItemClass<Any>] = [intItem, stringItem]

// Iterate over items and use `value` property as Any?
for item in array {
    let val: Any? = item.value
    // do something with val
}

// Individual usage with generic types support
let intValue: Int? = intItem.value
let stringValue: String? = stringItem.value

Why there is an error in array declaration like this:

Cannot convert value of type 'ItemClass<Int>' to expected element type 'ItemClass<Any>'

Upvotes: 0

Views: 48

Answers (1)

Rob Napier
Rob Napier

Reputation: 299355

This entire approach is incorrect, and you can see that by the consuming code:

// Iterate over items and use `value` property as Any?
for item in array {
    let val: Any? = item.value
    // do something with val
}

In that "do something with val," what can you possibly do? There are no methods on Any. If you're going to do something like as? T, then you've broken the whole point of the types, because you don't mean "any". You mean "some list of types I know about." If you want "some list of types I know about," that's an enum with associated data, not a protocol with an associated type.

enum Item {
    case string(String)
    case int(Int)

    var stringValue: String? {
        guard case .string(let value) = self else { return nil }
        return value
    }

    var intValue: Int? {
        guard case .int(let value) = self else { return nil }
        return value
    }
}

let intItem = Item.int(4)
let stringItem = Item.string("value")
let array: [Item] = [intItem, stringItem]

// Iterate over items and use `value` property as Any?
for item in array {
    switch item {
    case let .string(value): break // Do something with string
    case let .int(value): break // Do something with int
    }
}

// Individual usage with generic types support
let intValue: Int? = intItem.intValue
let stringValue: String? = stringItem.stringValue

If, on the other hand, you really mean "any type," then you're not going to be able to put them in a collection without hiding the values in a box that gets rid of any information about that type (i.e. "a type eraser"). Which you need comes down to your actual use case. There isn't a single answer; it's going to be driven by how you want to consume this data.

But if you need as? very much at all, you've done something wrong.

Upvotes: 1

Related Questions