How to check associated value's type

final public class SingleValue<Value> {
    public var value: Value
    public init(value: Value) {
        self.value = value
    }
}

public class ValueWrapper<Value> {
    public var value: Value
    public init(value: Value) {
        self.value = value
    }
}

let single = SingleValue<String>.init(value: "string")
let wrapper = ValueWrapper<Any>.init(value: single)

if wrapper.value is SingleValue<Any> {
    print("is SingleValue<Any>") // not invoked
}
if wrapper.value is SingleValue<String> {
    print("is SingleValue<String>") // print: is SingleValue<String>
}

Hi, All:
As described in the code above, I want to check if wrapper.value is SingleValue, but the test is failed. How can I do this properly?

yes, I just want to know if wrapper.value is SingleValue<...>, I don't care the associated value type of SingleValue.

Ah, SingleValue is a generic type (which generate types) and without a given type parameter it is not a concrete type. So you can't do that. SingleValue<Any> is also a concrete type, just like (but a different one than) SingleValue<String>.

You can have all SingleValue types conform to a protocol or inherit from a base class (whatever makes most sense for your use case) and check against that instead, eg:

protocol SingleValueProtocol {}
public class SingleValueBaseClass { }

final public class SingleValue<Value>: SingleValueBaseClass, SingleValueProtocol {
    public var value: Value
    public init(value: Value) {
        self.value = value
    }
}

public class ValueWrapper<Value> {
    public var value: Value
    public init(value: Value) {
        self.value = value
    }
}

let single = SingleValue<String>.init(value: "string")
let wrapper = ValueWrapper<Any>.init(value: single)

if wrapper.value is SingleValue<Any> { print("is SingleValue<Any>") } // not invoked
if wrapper.value is SingleValue<String> { print("is SingleValue<String>") } // invoked
if wrapper.value is SingleValueProtocol { print("conforms to SingleValueProtocol") } // invoked
if wrapper.value is SingleValueBaseClass {  print("is SingleValueBaseClass") } // invoked

Yeah, thanks for the solution! Just curious every type can be cast as Any, intuitively, this should work.

public protocol SingleValueProtocol {}
final public class SingleValue<Value> : SingleValueProtocol {
    public var value: Value
    public init(value: Value) {
        self.value = value
    }
}

public class ValueWrapper<Value> {
    public var value: Value
    public init(value: Value) {
        self.value = value
    }
}

public class SingleView {
    public var value: SingleValue<Any>
    public init(value: SingleValue<Any>) {
        self.value = value
    }
}

let single = SingleValue<String>.init(value: "string")
let wrapper = ValueWrapper<Any>.init(value: single)

if wrapper.value is SingleValueProtocol {
    let view = SingleView.init(value: wrapper.value as! SingleValue<Any>) // Error: Could not cast value of type 'SingleValue<Swift.String>' to 'SingleValue<Any>'
}

As described in the code above, I'm drilled into a trap, my SingleView doesn't want to introduce some associated type. So, even I knew wrapper.value is SingleValue<_>, but I still can not use it to init my SingleView.

Finally, I found the better solution, the code as follows:

final public class SingleValue<Value> {
    public var value: Value
    public init(value: Value) {
        self.value = value
    }
}

public class ValueWrapper<Value> {
    public var value: Value
    public init(value: Value) {
        self.value = value
    }
}

public class SingleView {
    public var value: SingleValue<Any>
    public init(value: SingleValue<Any>) {
        self.value = value
    }
}

let single = SingleValue<Any>.init(value: "string" as Any)
let wrapper = ValueWrapper<Any>.init(value: single)

if let v = wrapper.value as? SingleValue<Any> {
    let view = SingleView.init(value: v) //SingleView
}

But note that your last example only works because you defined single to be a SingleValue<Any> instead of a SingleValue<String> (as in your original example). Changing it back will make this solution it fail too:

let single = SingleValue<String>.init(value: "string") // <-- Changed to be as in original example
let wrapper = ValueWrapper<Any>.init(value: single)

if let v = wrapper.value as? SingleValue<Any> {
    let view = SingleView.init(value: v) //SingleView
}

Conversely, your original example would have worked (but you'd have the same drilling down problem because of is vs as?) if single had been declared as in your last example:

final public class SingleValue<Value> {
    public var value: Value
    public init(value: Value) {
        self.value = value
    }
}

public class ValueWrapper<Value> {
    public var value: Value
    public init(value: Value) {
        self.value = value
    }
}

//let single = SingleValue<String>.init(value: "string")
let single = SingleValue<Any>.init(value: "string" as Any)
let wrapper = ValueWrapper<Any>.init(value: single)

if wrapper.value is SingleValue<Any> {
    print("is SingleValue<Any>") // invoked
}
if wrapper.value is SingleValue<String> {
    print("is SingleValue<String>") // not invoked
}

I agree that there are some special cases which might lead your intuition that way, but look at the following example program and think about …

  • … why your intuition is true for Array and Optional but not for eg CollectionOfOne (or any generic type we define ourselves).

  • … the reason for the error on the last line.

func check<T>(if value: Any, isType _: T.Type) {
    if value is T {
        print("Value of type \(type(of: value)) is \(T.self)")
    } else {
        print("Value of type \(type(of: value)) is not \(T.self)")
    }
}

let arrInt = Array<Int>()
let cooInt = CollectionOfOne<Int>(123)
let optInt = Optional<Int>.none
let rngInt = Range<Int>(uncheckedBounds: (lower: 0, upper: 1))

check(if: arrInt, isType: Array<Any>.self)
check(if: cooInt, isType: CollectionOfOne<Any>.self)
check(if: optInt, isType: Optional<Any>.self) // WARNING: Expression implicitly coerced from 'Optional<Int>' to 'Any'
//check(if: rngInt, isType: Range<Any>.self) // ERROR: Type 'Any' does not conform to protocol 'Comparable'

which will print:

Value of type           Array<Int> is               Array<Any>
Value of type CollectionOfOne<Int> is not CollectionOfOne<Any>
Value of type        Optional<Int> is            Optional<Any>
1 Like