49

I know that I can check the type of a var in Swift with is

if item is Movie {
    movieCount += 1
} else if item is Song {
    songCount += 1
}

but how can I check that two instances have the same class? The following does not work:

if item1 is item2.dynamicType {
    print("Same subclass")
} else {
    print("Different subclass)
}

I could easily add a "class" function and update it in each subclass to return something unique, but that seems like a kludge...

Grimxn
  • 22,115
  • 10
  • 72
  • 85
  • 1
    Usually, such comparison is no needed in Swift. – Sulthan Jun 11 '14 at 12:03
  • Well, the reason I used "subclass" rather than "class" in the example is the clue - it's quite common to have several interacting subclasses that are generally treated similarly, but do something special when they are the same - say for example subclasses of Animal which interact (move away from, say) all other subclasses, but not their own... – Grimxn Jun 11 '14 at 12:47
  • No, it that case you check that both classes are `Animal`, you don't normally check that their classes are the same. If you have different classes with the same interface (they can interact with each other) but you don't want them to interact with each other in some cases then there is something seriously wrong with your design. – Sulthan Jun 11 '14 at 12:51
  • If you are still open minded for a pure _Swift_ solution for `AnyObject`, please take a look on my answer. – holex Jun 11 '14 at 14:18
  • Excellent - many thanks! I've changed the accepted answer to yours. – Grimxn Jun 11 '14 at 16:24
  • Depending on what you want to actually do with two objects of the same type, [generics](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Generics.html#//apple_ref/doc/uid/TP40014097-CH26-XID_234) might be a good solution. – rickster Jun 11 '14 at 19:35
  • I thought about using generics, but I couldn't see a way to do it with things which may or may not be the same class. – Grimxn Jun 12 '14 at 10:18

8 Answers8

51

Swift 3.0 (also works with structs)

if type(of: someInstance) == type(of: anotherInstance) {
    print("matching type")
} else {
    print("something else")
}
T. Benjamin Larsen
  • 6,373
  • 4
  • 22
  • 32
  • This doesn't work for me. var type = type(of: visibleController as! ContactsBaseVC ) var type2 = type(of: self as! ContactsBaseVC) if type(of: self) == type(of: visibleController ){ print("Add Contact Presed") } – i.jameelkhan Oct 25 '17 at 19:05
  • It's a bit hard to decipher the problem without more context, but if either of those controllers are downcast to the type ContactBaseVC the underlying type would still be the original type, _not_ the one you have downcast to. – T. Benjamin Larsen Oct 26 '17 at 04:48
48

I also answered How do you find out the type of an object (in Swift)? to point out that at some point Apple added support for the === operator to Swift Types, so the following will now work:

if item1.dynamicType === item2.dynamicType {
    print("Same subclass")
} else {
    print("Different subclass")
}

This works even without importing Foundation, but note it will only work for classes, as structs have no dynamic type.

Community
  • 1
  • 1
Alex Pretzlav
  • 15,505
  • 9
  • 57
  • 55
  • 1
    The code in this answer no longer compiles with Swift 2.0 – ndmeiri Dec 18 '15 at 06:18
  • Thanks for the notice @ndmeiri, I've updated my example for Swift 2.0. – Alex Pretzlav Dec 21 '15 at 02:12
  • @AlexPretzlav Even with the changes, it does not compile. The compiler says that the === binary operator needs different arguments. – ndmeiri Dec 21 '15 at 02:38
  • @ndmeiri are both of your `item` instances of classes and not structs? It works for me in pure Swift, see this gist: https://gist.github.com/Pretz/e7b986ccefff8b83b379 – Alex Pretzlav Dec 21 '15 at 02:58
  • @AlexPretzlav Hmm that's strange. That compiles fine. But the following does not compile for me, and I'm not sure how it's different from your example: `if UIColor().dynamicType === UIFont().dynamicType { }` The error is "/Users/Naji/Developer/penji-ios/Penji/Penji/BannerVendor.swift:34:34: Binary operator '===' cannot be applied to operands of type 'UIColor.Type' and 'UIFont.Type'" – ndmeiri Dec 21 '15 at 03:10
  • 1
    @ndmeiri Interesting! Looks like the compiler is being extra clever and figuring out that those types cannot be identical. If you assign to variables first, it works: `let item1 = UIColor(), item2 = UIFont(); if item1.dynamicType === item2.dynamicType {` Also, you can do things like `item1.dynamicType === UIColor.self` to check against a known class (note that `UIColor()` does _not_ return a direct instance of `UIColor`, so the result is `false`) – Alex Pretzlav Dec 21 '15 at 23:14
  • @AlexPretzlav Hmm you're right! That is strange. Perhaps the answer should be updated to reflect that caveat? – ndmeiri Dec 21 '15 at 23:58
  • 7
    `dynamicType` has been deprecated. use `type(of: ...)` instead – utahwithak Feb 10 '17 at 16:06
  • 1
    @T. Benjamin Larsen's answer is correct for Swift 4. – ThomasW May 11 '18 at 01:37
  • 1
    Lovely: '.dynamicType' is deprecated. Use 'type(of: ...)' instead – Klajd Deda Jun 01 '18 at 17:41
34

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
Ondrej Rafaj
  • 4,342
  • 8
  • 42
  • 65
holex
  • 23,961
  • 7
  • 62
  • 76
  • 1
    Perfect! That'll do very nicely! – Grimxn Jun 11 '14 at 16:25
  • 11
    This isn't actually "pure Swift", since `object_getClassName` is an ObjC runtime function. (And as such, it won't work on Swift types that can't bridge to ObjC classes.) – rickster Jun 11 '14 at 19:32
  • @rickster, it perfectly works, because e.g. `object_getClassName` is available in _Swift_ for _Swift_ objects specifically. Apple has provided the interface for them for developers, however the background implementation could be the same as is for Obj-C; but that is just a guess really. – holex Jun 12 '14 at 08:04
  • 2
    That function comes from the Objective-C runtime, so it's not available in Swift unless you `import Foundation` (or `import Cocoa` or `import UIKit`). And it doesn't work for non-ObjC Swift types. It's a good solution... just trying to temper expectations. – rickster Jun 12 '14 at 16:48
  • @rickster, it works with all _Swift_ types properly, I guess you should test it first before you continue. I have never told that it is not coming from Obj-C runtime methods, I have told only the interfaces are also provided for _Swift_ by Apple. you cannot use those methods in Obj-C if you are not importing the related headers, so that argument does not really count, because from this perspective the "pure" _Swift_ is literally useless for any type of development currently, without those frameworks. so, the border is not quote sharp, but we can argue it on forever. :) – holex Jun 12 '14 at 17:14
  • 1
    Not all Swift types bridge to ObjC objects, even if they can behave like objects otherwise. Test: `struct S {}; let a = S(); println(object_getClassName(a))` You can do plenty of useful development in "pure" Swift — even if your UI code needs to work with Cocoa frameworks, it's perfectly reasonable to have model classes in files that don't import anything. I never said your solution isn't broadly workable, just that readers should not expect it to be universal. – rickster Jun 12 '14 at 18:22
  • @rickster, I'm not seeing the problem with your test, can you elaborate, please, what you are exactly not happy with? it works perfectly on my machine. that class `S` won't match with anything else but another instance of `S`. I think further argument is not necessary, it seems probably you don't understand or can't understand what you printed out, but that is out of this topic...... – holex Jun 13 '14 at 06:21
  • 1
    Condescension isn't really appropriate for SO. Did you run the test code in my previous comment? – rickster Jun 13 '14 at 16:43
  • @rickster, I see your issue now, let me tell you the classes and structs are **not** the same in _Swift_. probably you have read it in the official docs, but for safety sake I'll quote one of the features about what a `class` can do, and the `struct` can not in _Swift_: _"Type casting enables you to check and interpret the type of a class instance at runtime."_ if you'd like to complain about it, I encourage you to contact to Apple directly for further discussion and you can tell him your point, but please do not off the thread because the language barriers are not part of the thread. – holex Jun 14 '14 at 08:46
  • Down voted because the === operator described by Grimxn is so much easier than any of your answers – Lucas van Dongen Oct 17 '15 at 15:43
12

Swift 3 - pay attention that comparing instances is not the same as checking if an istance is of a given type:

struct Model {}

let modelLhs = Model()
let modelRhs = Model()
type(of: modelLhs) == type(of: modelRhs) //true
type(of: modelLhs) == type(of: Model.self) //false
modelLhs is Model //true
andreacipriani
  • 2,519
  • 26
  • 23
4

For subclasses of NSObject, I went with:

let sameClass: Bool = instance1.classForCoder == instance2.classForCoder

Another caveat of this method,

The private subclasses of a class cluster substitute the name of their public superclass when being archived.

Apple documentation

Tom Howard
  • 4,672
  • 2
  • 43
  • 48
  • For some reason that is the only thing that worked with ViewControllers, others just always return UIViewController class instead of a class that inherits from it – Simon Moshenko Feb 13 '20 at 13:35
2

In Swift 5 you can test whether one value is the same type as another, if one of the values is 'self', by using 'Self', its type. This works with structs, as you can see below.

(I am actually here looking for a way to do this without Self as I am trying to refactor something. The 'type(of: ship)' method mentioned above, looks like the most general way to do this. I have tested that it works with the example below.)

protocol Ship { var name: String {get} }

extension Ship {
    func isSameClass(as other: Ship) -> Bool {
        return other is Self
    }
}

struct Firefly: Ship { var name: String }
struct Trebuchet: Ship { var name: String }

func speak(about s1: Ship, and s2: Ship) {
    print(  "\(s1.name) and \(s2.name) are "
        +   "\(s1.isSameClass(as: s2) ? "" : "not ")"
        +   "the same class." )
}

func talk(about s1: Ship, and s2: Ship) {
    print(  "\(s1.name) and \(s2.name) are "
        +   "\(type(of: s1) == type(of: s2) ? "" : "not ")"
        +   "the same class." )
}

var serenity = Firefly(name: "Serenity")
var saffron = Firefly(name: "Saffron")
var inara = Trebuchet(name: "Inara")

speak(about: serenity, and: saffron)    // Serenity and Saffron are the same class.
speak(about: serenity, and: inara)      // Serenity and Inara are not the same class.
talk(about: serenity, and: saffron)     // Serenity and Saffron are the same class.
talk(about: serenity, and: inara)       // Serenity and Inara are not the same class.
adazacom
  • 443
  • 3
  • 9
1

i'm using this, looks helpful for me: it returns true only if all objects are of same type;

func areObjects<T>(_ objects: [Any], ofType: T.Type) -> Bool {
    for object in objects {
        if !(object is T) {
            return false
        }
    }
    return true
}
user3206558
  • 392
  • 1
  • 12
0

At the moment Swift types have no introspection, so there is no builtin way to get the type of an instance. instance.className works for Objc classes.

Joseph Mark
  • 9,298
  • 4
  • 29
  • 31