324

I have an array that is made up of AnyObject. I want to iterate over it, and find all elements that are array instances.

How can I check if an object is of a given type in Swift?

mfaani
  • 33,269
  • 19
  • 164
  • 293
Encore PTL
  • 8,084
  • 10
  • 43
  • 78
  • 1
    Your question asks about finding the type of a given object, but you've accepted an answer that's only capable of checking whether an object is of a given type. I suggest you edit your question to specifically that, otherwise many readers will be dissatisfied with the answer you have accepted. (All of the other answers are similar, so luckily you don't need to be concerned about making them invalid by narrowing your question.) – Jeremy Jun 07 '14 at 04:47
  • 1
    I have edited this question to disambiguate it from http://stackoverflow.com/q/24093433, which I am voting to reopen. They are both useful, similar, questions, but the answers are quite distinct so it would be useful to keep them separated. – Jeremy Jun 07 '14 at 17:44
  • 1
    possible duplicate of [How do you find out the type of an object (in Swift)?](http://stackoverflow.com/questions/24101450/how-do-you-find-out-the-type-of-an-object-in-swift) – Esqarrouth May 19 '15 at 17:30

20 Answers20

353

If you want to check against a specific type you can do the following:

if let stringArray = obj as? [String] {
    // obj is a string array. Do something with stringArray
}
else {
    // obj is not a string array
}

You can use "as!" and that will throw a runtime error if obj is not of type [String]

let stringArray = obj as! [String]

You can also check one element at a time:

let items : [Any] = ["Hello", "World"]
for obj in items {
   if let str = obj as? String {
      // obj is a String. Do something with str
   }
   else {
      // obj is not a String
   }
}
GoZoner
  • 67,920
  • 20
  • 95
  • 145
drewag
  • 93,393
  • 28
  • 139
  • 128
  • 1
    Why would that only throw a runtime error and not a compile time error when the `?` is not present. It sounds like `as` and `?` when combined will perform runtime check. When would be appropriate to use `as` without `?`? Thanks in advance. – Unheilig Jun 06 '14 at 23:52
  • @Unheilig You should only use `as` without the `?` if there is no way your program could recover from the object not being of that type because the program will immediately halt if it is not. Using the `?` in the `if` statement allows the program to continue. – drewag Jun 06 '14 at 23:55
  • Thanks for response. Correct me if I am wrong: I thought that using the `?` in this case would perform a "generic" type check, if yes, to the if clause, if not, to the else clause. Without the `?` else would never be entered and as you pointed out cause a runtime error. Thanks again. – Unheilig Jun 07 '14 at 00:00
  • @Unheilig I'm sorry, I don't understand what you are saying / asking. The `?` allows the assignment to return `nil` causing the if statement to return `false` and therefore falling through to the else statement. However, I think that explanation helps with the understanding, but `if let` is actually a special case in the compiler – drewag Jun 07 '14 at 00:04
  • It's ok, you understood me. Your last comment reflects that. Thanks. Am now curious: `if let` is a special case to the compiler? How so? – Unheilig Jun 07 '14 at 00:07
  • @Unheilig it is a special case in that normally assignment (`=`) no longer returns a value like it does in C. This is supposed to prevent errors of accidentally using `=` instead of `==` for if statements. – drewag Jun 07 '14 at 00:33
  • Thanks for your clarification. Just one more thing `if var` and `if let` in this case _in_ `if` shouldn't make any difference, right? I know the difference, just talking about when they are in `if`. – Unheilig Jun 07 '14 at 00:38
  • 1
    @Unheilig Correct, you can use var if you would like to be able to modify the value while in that local scope (those changes will not affect outside the scope) – drewag Jun 07 '14 at 00:40
261

In Swift 2.2 - 5 you can now do:

if object is String
{
}

Then to filter your array:

let filteredArray = originalArray.filter({ $0 is Array })

If you have multiple types to check:

    switch object
    {
    case is String:
        ...

    case is OtherClass:
        ...

    default:
        ...
    }
meaning-matters
  • 21,929
  • 10
  • 82
  • 142
  • 1
    This solution is shorter, but it has a disadvantage: you can't use the `object` as a `String` inside the braces (at least in Swift 2), while with the `let` solution you can do it. – Ferran Maylinch Jan 18 '17 at 16:47
  • 1
    @FerranMaylinch Don't understand what you mean because using `object` in the block is fine. – meaning-matters Jan 18 '17 at 16:51
  • @meaning-matters e.g. you won't be able to do `object.uppercaseString` because the type of the variable is not casted to that type, you just checked that the object (pointed by the variable) is a `String` – Ferran Maylinch Jan 18 '17 at 16:53
  • How can you do this if your class type you are checking for is arbitrary? If you only have a variable you need to get a class type from? – Alex Zavatone May 24 '19 at 16:20
159

If you only want to know if an object is a subtype of a given type then there is a simpler approach:

class Shape {}
class Circle : Shape {}
class Rectangle : Shape {}

func area (shape: Shape) -> Double {
  if shape is Circle { ... }
  else if shape is Rectangle { ... }
}

“Use the type check operator (is) to check whether an instance is of a certain subclass type. The type check operator returns true if the instance is of that subclass type and false if it is not.” Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks.

In the above the phrase 'of a certain subclass type' is important. The use of is Circle and is Rectangle is accepted by the compiler because that value shape is declared as Shape (a superclass of Circle and Rectangle).

If you are using primitive types, the superclass would be Any. Here is an example:

 21> func test (obj:Any) -> String {
 22.     if obj is Int { return "Int" }
 23.     else if obj is String { return "String" }
 24.     else { return "Any" }
 25. } 
 ...  
 30> test (1)
$R16: String = "Int"
 31> test ("abc")
$R17: String = "String"
 32> test (nil)
$R18: String = "Any"
GoZoner
  • 67,920
  • 20
  • 95
  • 145
  • 2
    What if I stored a primitive type in an array or if the array is one of the primitive type, would `is` still work here? Thanks. – Unheilig Jun 07 '14 at 01:04
  • It should work if you declare the `object` as `Any`. Updated with an example. – GoZoner Jun 07 '14 at 01:10
  • Thanks for reply. It looks promising. My only doubt is that according to the answer below, in which `AnyObject` is suggested, seems to have been retorted due to `AnyObject` not inheriting from `NSObject`. If `Any` is different, then this would be in fact a great solution as well. Thanks. – Unheilig Jun 07 '14 at 01:29
27

for swift4:

if obj is MyClass{
    // then object type is MyClass Type
}
Ahmad Labeeb
  • 1,056
  • 11
  • 21
23

I have 2 ways of doing it:

if let thisShape = aShape as? Square 

Or:

aShape.isKindOfClass(Square)

Here is a detailed example:

class Shape { }
class Square: Shape { } 
class Circle: Shape { }

var aShape = Shape()
aShape = Square()

if let thisShape = aShape as? Square {
    println("Its a square")
} else {
    println("Its not a square")
}

if aShape.isKindOfClass(Square) {
    println("Its a square")
} else {
    println("Its not a square")
}

Edit: 3 now:

let myShape = Shape()
if myShape is Shape {
    print("yes it is")
}
Esqarrouth
  • 38,543
  • 21
  • 161
  • 168
  • 2
    `isKindOfClass` is a method of the `NSObject` protocol; it should only work for classes that adopt it (all classes descending from NSObject, plus any custom Swift class that adopts it explicitly) – Nicolas Miari Jul 20 '16 at 01:39
10

Assume drawTriangle is an instance of UIView.To check whether drawTriangle is of type UITableView:

In Swift 3,

if drawTriangle is UITableView{
    // in deed drawTriangle is UIView
    // do something here...
} else{
    // do something here...
}

This also could be used for classes defined by yourself. You could use this to check subviews of a view.

Yushan ZHANG
  • 523
  • 5
  • 18
7

Just for the sake of completeness based on the accepted answer and some others:

let items : [Any] = ["Hello", "World", 1]

for obj in items where obj is String {
   // obj is a String. Do something with str
}

But you can also (compactMap also "maps" the values which filter doesn't):

items.compactMap { $0 as? String }.forEach{ /* do something with $0 */ ) }

And a version using switch:

for obj in items {
    switch (obj) {
        case is Int:
           // it's an integer
        case let stringObj as String:
           // you can do something with stringObj which is a String
        default:
           print("\(type(of: obj))") // get the type
    }
}

But sticking to the question, to check if it's an array (i.e. [String]):

let items : [Any] = ["Hello", "World", 1, ["Hello", "World", "of", "Arrays"]]

for obj in items {
  if let stringArray = obj as? [String] {
    print("\(stringArray)")
  }
}

Or more generally (see this other question answer):

for obj in items {
  if obj is [Any] {
    print("is [Any]")
  }

  if obj is [AnyObject] {
    print("is [AnyObject]")
  }

  if obj is NSArray {
    print("is NSArray")
  }
}
FranMowinckel
  • 4,233
  • 1
  • 30
  • 26
6

as? won't always give you the expected result because as doesn't test if a data type is of a specific kind but only if a data type can be converted to or represented as specific kind.

Consider this code for example:

func handleError ( error: Error ) {
    if let nsError = error as? NSError {

Every data type conforming to the Error protocol can be converted to a NSError object, so this will always succeed. Yet that doesn't mean that error is in fact a NSError object or a subclass of it.

A correct type check would be:

func handleError ( error: Error ) {
    if type(of: error) == NSError.self {

However, this checks for the exact type only. If you want to also include subclasses of NSError, you should use:

func handleError ( error: Error ) {
    if error is NSError.Type {
Mecki
  • 125,244
  • 33
  • 244
  • 253
5

Why not use the built in functionality built especially for this task?

let myArray: [Any] = ["easy", "as", "that"]
let type = type(of: myArray)

Result: "Array<Any>"
Patrik Forsberg
  • 622
  • 9
  • 8
5

Be warned about this:

var string = "Hello" as NSString
var obj1:AnyObject = string
var obj2:NSObject = string

print(obj1 is NSString)
print(obj2 is NSString)
print(obj1 is String)
print(obj2 is String) 

All of the four last lines return true, this is because if you type

var r1:CGRect = CGRect()
print(r1 is String)

... it prints "false" of course, but a Warning says that the Cast from CGRect to String fails. So some type are bridged, ans the 'is' keyword calls an implicit cast.

You should better use one of these:

myObject.isKind(of: MyClass.self)) 
myObject.isMember(of: MyClass.self))
tontonCD
  • 320
  • 2
  • 6
4

You can use this function and then call it:

func printInfo(_ value: Any) {
    let t = type(of: value)
    print("'\(value)' of type '\(t)'")
}

such as: printInfo(data)

'125 bytes' of type 'Data'

pkamb
  • 33,281
  • 23
  • 160
  • 191
ACDA100
  • 43
  • 4
2

myObject as? String returns nil if myObject is not a String. Otherwise, it returns a String?, so you can access the string itself with myObject!, or cast it with myObject! as String safely.

cprcrack
  • 17,118
  • 7
  • 88
  • 91
2

If you just want to check the class without getting a warning because of the unused defined value (let someVariable ...), you can simply replace the let stuff with a boolean:

if (yourObject as? ClassToCompareWith) != nil {
   // do what you have to do
}
else {
   // do something else
}

Xcode proposed this when I used the let way and didn't use the defined value.

the_mariooo
  • 113
  • 1
  • 6
2

Why not to use something like this

fileprivate enum types {
    case typeString
    case typeInt
    case typeDouble
    case typeUnknown
}

fileprivate func typeOfAny(variable: Any) -> types {
    if variable is String {return types.typeString}
    if variable is Int {return types.typeInt}
    if variable is Double {return types.typeDouble}
    return types.typeUnknown
}

in Swift 3.

Dawy
  • 770
  • 6
  • 23
2

Swift 4.2 , In my case , using isKind function .

isKind(of:) Returns a Boolean value that indicates whether the receiver is an instance of given class or an instance of any class that inherits from that class.

  let items : [AnyObject] = ["A", "B" , ... ]
  for obj in items {
    if(obj.isKind(of: NSString.self)){
      print("String")
    }
  }

Readmore https://developer.apple.com/documentation/objectivec/nsobjectprotocol/1418511-iskind

Tung Tran
  • 439
  • 6
  • 8
  • 2
    That is not Swift. It is Cocoa and works only where it would work for Objective C. – matt Apr 01 '19 at 02:39
1

Swift 3:

class Shape {}
class Circle : Shape {}
class Rectangle : Shape {}

if aShape.isKind(of: Circle.self) {
}
Elijah
  • 8,381
  • 2
  • 55
  • 49
1

Swift 5.2 & Xcode Version:11.3.1(11C504)

Here is my solution of checking data type:

 if let typeCheck = myResult as? [String : Any] {
        print("It's Dictionary.")
    } else { 
        print("It's not Dictionary.") 
    }

I hope it will help you.

Spencer Reid
  • 409
  • 3
  • 14
  • 1
    When answering an old question, your answer would be much more useful to other StackOverflow users if you included some context to explain how your answer helps, particularly for a question that already has an accepted answer. See: [How do I write a good answer](https://stackoverflow.com/help/how-to-answer). – David Buck Jan 21 '20 at 11:27
0

If you have Response Like This:

{
  "registeration_method": "email",
  "is_stucked": true,
  "individual": {
    "id": 24099,
    "first_name": "ahmad",
    "last_name": "zozoz",
    "email": null,
    "mobile_number": null,
    "confirmed": false,
    "avatar": "http://abc-abc-xyz.amazonaws.com/images/placeholder-profile.png",
    "doctor_request_status": 0
  },
  "max_number_of_confirmation_trials": 4,
  "max_number_of_invalid_confirmation_trials": 12
}

and you want to check for value is_stucked which will be read as AnyObject, all you have to do is this

if let isStucked = response["is_stucked"] as? Bool{
  if isStucked{
      print("is Stucked")
  }
  else{
      print("Not Stucked")
 }
}
Atef
  • 2,872
  • 1
  • 36
  • 32
0

If you don't know that you will get an array of dictionaries or single dictionary in the response from server you need to check whether the result contains an array or not.
In my case always receiving an array of dictionaries except once. So, to handle that I used the below code for swift 3.

if let str = strDict["item"] as? Array<Any>

Here as? Array checks whether the obtained value is array (of dictionary items). In else case you can handle if it is single dictionary item which is not kept inside an array.

cezar
  • 11,616
  • 6
  • 48
  • 84
V.S
  • 79
  • 1
  • 5
0
let originalArray : [Any?] = ["Hello", "World", 111, 2, nil, 3.34]
let strings = originalArray.compactMap({ $0 as? String })

print(strings)
//printed: ["Hello", "World"]