How to Check Whether an Object Is Kind of a Dynamic Class Type in Swift

How to check whether an object is kind of a dynamic class type in swift?

Personally, I think @JeremyP's suggestion to use Mirror is the best; though I would make a couple of tweaks to it:

/// Conditionally cast `x` to a given dynamic metatype value, taking into consideration
/// class inheritance hierarchies.
func conditionallyCast<T, U>(_ x: T, to destType: U.Type) -> U? {

if type(of: x) is AnyClass && destType is AnyClass { // class-to-class

let isCastable = sequence(
first: Mirror(reflecting: x), next: { $0.superclassMirror }
)
.contains { $0.subjectType == destType }

return isCastable ? (x as! U) : nil
}

// otherwise fall back to as?
return x as? U
}

Here we're using sequence(first:next:) to create a sequence of metatypes from the dynamic type of x through any superclass metatypes it might have (probably the first use of the function I've seen that doesn't look awful :P). In addition, we're falling back to doing an as? cast when we know we're not doing a class-to-class cast, which allows the function to also work with protocol metatypes.

Then you can simply say:

extension Sequence {
func ofType<T>(_ metatype: T.Type) -> [T] {
return flatMap { conditionallyCast($0, to: metatype) }
}
}

protocol P {}
class Animal {}
class Mammal: Animal {}
class Monkey: Mammal, P {}
class Pig: Mammal {}
class Human: Mammal, P {}

let animals = [Monkey(), Pig(), Human(), Mammal(), Animal()]

let animalType: Animal.Type = Mammal.self
print(animals.ofType(animalType)) // [Monkey, Pig, Human, Mammal]

print(animals.ofType(P.self)) // [Monkey, Human]

Another option, assuming you're on an Apple platform (i.e have access to the Objective-C runtime), is to use the the Objective-C metaclass method isSubclass(of:) in order to check if a given metatype is equal, or is a subclass of another:

import Foundation

/// Conditionally cast `x` to a given dynamic metatype value, taking into consideration
/// class inheritance hierarchies.
func conditionallyCast<T, U>(_ x: T, to destType: U.Type) -> U? {

let sourceType = type(of: x)

if let sourceType = sourceType as? AnyClass,
let destType = destType as? AnyClass { // class-to-class

return sourceType.isSubclass(of: destType) ? (x as! U) : nil
}

// otherwise fall back to as?
return x as? U
}

This works because on Apple platforms, Swift classes are built on top of Obj-C classes – and therefore the metatype of a Swift class is an Obj-C metaclass object.

Check whether Swift object is an instance of a given metatype

Unfortunately, you can currently only use a named type with the is operator, you cannot yet use an arbitrary metatype value with it (although really IMO you ought to be able to).

Assuming you have control over the creation of the metatypes that you want to compare against, one solution that achieves the same result would be to create a wrapper type with an initialiser that stores a closure that performs the is check against a generic placeholder:

struct AnyType {

let base: Any.Type
private let _canCast: (Any) -> Bool

/// Creates a new AnyType wrapper from a given metatype.
/// The passed metatype's value **must** match its static value,
/// i.e `T.self == base`.
init<T>(_ base: T.Type) {
precondition(T.self == base, """
The static value \(T.self) and dynamic value \(base) of the passed \
metatype do not match
""")

self.base = T.self
self._canCast = { $0 is T }
}

func canCast<T>(_ x: T) -> Bool {
return _canCast(x)
}
}

protocol P {}
class C : P {}
class D : C {}

let types = [
AnyType(P.self), AnyType(C.self), AnyType(D.self), AnyType(String.self)
]

for type in types {
print("C instance can be typed as \(type.base): \(type.canCast(C()))")
print("D instance can be typed as \(type.base): \(type.canCast(D()))")
}

// C instance can be typed as P: true
// D instance can be typed as P: true
// C instance can be typed as C: true
// D instance can be typed as C: true
// C instance can be typed as D: false
// D instance can be typed as D: true
// C instance can be typed as String: false
// D instance can be typed as String: false

The only limitation of this approach is that given we're performing the is check with T.self, we have to enforce that T.self == base. For example, we cannot accept AnyType(D.self as C.Type), as then T.self would be C.self while base would be D.self.

However this shouldn't be a problem in your case, as we're just constructing AnyType from metatypes that are known at compile time.


If however you don't have control over the creation of the metatypes (i.e you get handed them from an API), then you're quite a bit more limited with what you can do with them.

As @adev says, you can use type(of:) in order to get the dynamic metatype of a given instance, and the == operator to determine if two metatypes are equivalent. However, one problem with this approach is that it disregards both class hierarchies and protocols, as a subtype metatypes will not compare equal with a supertype metatypes.

One solution in the case of classes is to use Mirror, as also shown in this Q&A:

/// Returns `true` iff the given value can be typed as the given
/// **concrete** metatype value, `false` otherwise.
func canCast(_ x: Any, toConcreteType destType: Any.Type) -> Bool {
return sequence(
first: Mirror(reflecting: x), next: { $0.superclassMirror }
)
.contains { $0.subjectType == destType }
}

class C {}
class D : C {}

print(canCast(D(), toConcreteType: C.self)) // true
print(canCast(C(), toConcreteType: C.self)) // true
print(canCast(C(), toConcreteType: D.self)) // false
print(canCast(7, toConcreteType: Int.self)) // true
print(canCast(7, toConcreteType: String.self)) // false

We're using sequence(first:next:) to create a sequence of metatypes from the dynamic type of x through any superclass metatypes it might have.

However this method still won't work with protocols. Hopefully a future version of the language will provide much richer reflection APIs that allow you to compare the relationship between two metatype values.


However, given the above knowledge of being able to use Mirror, we can use it to lift the aforementioned restriction of T.self == base from our AnyType wrapper on by handling class metatypes separately:

struct AnyType {

let base: Any.Type
private let _canCast: (Any) -> Bool

/// Creates a new AnyType wrapper from a given metatype.
init<T>(_ base: T.Type) {

self.base = base

// handle class metatypes separately in order to allow T.self != base.
if base is AnyClass {
self._canCast = { x in
sequence(
first: Mirror(reflecting: x), next: { $0.superclassMirror }
)
.contains { $0.subjectType == base }
}
} else {
// sanity check – this should never be triggered,
// as we handle the case where base is a class metatype.
precondition(T.self == base, """
The static value \(T.self) and dynamic value \(base) of the passed \
metatype do not match
""")

self._canCast = { $0 is T }
}
}

func canCast<T>(_ x: T) -> Bool {
return _canCast(x)
}
}

print(AnyType(D.self as C.Type).canCast(D())) // true

The case where T.self is a class metatype should be the only case where T.self != base, as with protocols, when T is some protocol P, T.Type is P.Protocol, which is the type of the protocol itself. And currently, this type can only hold the value P.self.

How do you find out the type of an object (in Swift)?

Swift 3 version:

type(of: yourObject)

How to check two instances are the same class/type in swift

I feel necessary to quote from the Swift Programming Language documentation first of all:

Classes have additional capabilities that structures do not:

  • Type casting enables you to check and interpret the type of a class instance at runtime.

According to this, it may be helpful for someone in the future:

func areTheySiblings(class1: AnyObject!, class2: AnyObject!) -> Bool {
return object_getClassName(class1) == object_getClassName(class2)
}

and the tests:

let myArray1: Array<AnyObject> = Array()
let myArray2: Array<Int> = Array()
let myDictionary: Dictionary<String, Int> = Dictionary()
let myString: String = String()

let arrayAndArray: Bool = self.areTheySiblings(myArray1, class2: myArray2) // true
let arrayAndString: Bool = self.areTheySiblings(myArray1, class2: myString) // false
let arrayAndDictionary: Bool = self.areTheySiblings(myArray1, class2: myDictionary) // false

UPDATE

you also can overload a new operator for doing such a thing, like e.g. this:

infix operator >!<

func >!< (object1: AnyObject!, object2: AnyObject!) -> Bool {
return (object_getClassName(object1) == object_getClassName(object2))
}

and the results:

println("Array vs Array: \(myArray1 >!< myArray2)") // true
println("Array vs. String: \(myArray1 >!< myString)") // false
println("Array vs. Dictionary: \(myArray1 >!< myDictionary)") // false

UPDATE#2

you can also use it for your own new Swift classes, like e.g. those:

class A { }
class B { }

let a1 = A(), a2 = A(), b = B()

println("a1 vs. a2: \(a1 >!< a2)") // true
println("a1 vs. b: \(a1 >!< b)") // false

Swift Check Dynamic Type of Array of Objects

You can keep Any instances and compare for Any.Type types with the == operator. Example based on the code in @lassej answer:

let array: [Any] = [UIView(), "hellow", 12, true]
let types: [(Any.Type, String)] = [
(UIView.self, "UIView"),
(String.self, "String"),
(Int.self, "Integer")
]

anyLoop: for any in array {
for (type, name) in types {
if any.dynamicType == type {
print( "type: \(name)")
continue anyLoop
}
}
print( "unknown type: \(any.dynamicType)")
}

// Prints:
// type: UIView
// type: String
// type: Integer
// unknown type: Bool

Get the type of AnyObject dynamically in Swift

Typically this is what generics are for. There is seldom good reason for having an AnyObject in code that doesn't interact with ObjC. If you're then performing different actions based on the type, then you probably actually meant to use overloading.

That said, there are several ways to get access to the type. Typically you want to run different code depending on the type, so you can use a switch for that:

let x:AnyObject = "asdf"
switch x {
case is String: println("I'm a string")
default: println("I'm not a string")
}

or

let x:AnyObject = "asdf"
switch x {
case let xString as String: println("I'm a string: \(xString)")
default: println("I'm not a string")
}

Or you can use an if:

if let string = x as? String {
println("I'm a string: \(string)")
}

See "Type Casting for Any and AnyObject" in the Swift Programming Language for more discussion.

But again, unless you're working with ObjC code, there is seldom reason to use Any or AnyObject. Generics and overloads are the tools designed to solve those problems in Swift.



Related Topics



Leave a reply



Submit