4

I'm trying to check if an object is of a given type and I'm getting an error:

'expectedClass' is not a type

xcode

My code below.

func testInputValue(inputValue: AnyObject, isKindOfClass expectedClass: AnyClass) throws {
    guard let object = inputValue as? expectedClass else {
        // Throw an error
        let userInfo = [NSLocalizedDescriptionKey: "Expected an inputValue of type \(expectedClass), but got a \(inputValue.dynamicType)"]
        throw NSError(domain: RKValueTransformersErrorDomain, code: Int(RKValueTransformationError.UntransformableInputValue.rawValue), userInfo: userInfo)
    }
}

I'm trying to figure out what can be wrong here.

JAL
  • 41,701
  • 23
  • 172
  • 300
Rafał Sroka
  • 39,540
  • 23
  • 113
  • 143
  • You can not store a `Type` in variable and use it for `TypeCasting`, it needs to be THE class, so you will need to replace `expectedClass` with the actual class say `String`, `NSObject` etc. – parveen Apr 25 '16 at 20:26
  • Related: [Cast to a metatype type in Swift](http://stackoverflow.com/q/27882049) – jscs Apr 25 '16 at 20:38

3 Answers3

3

You should be able to do this with generics:

func testInputValue<T>(inputValue: AnyObject, isKindOfClass expectedClass: T.Type) throws {
    guard let object = inputValue as? T else {
        ...
    }
}
dan
  • 9,695
  • 1
  • 42
  • 40
  • I think this is the right way to do it, but shouldn't `inputValue` be typed as `T` rather than `AnyObject`? – jscs Apr 25 '16 at 20:40
  • 1
    @JoshCaswell The issue with `inputValue` being `T` is that you'll get the warning: "Conditional cast from 'T' to 'T' always succeeds (I think). – JAL Apr 25 '16 at 20:42
  • You wouldn't be able to call the method in a way that would make it throw the error if `inputValue` was type `T`, which would make it kind of pointless. – dan Apr 25 '16 at 20:43
  • That's what I thought, @dan, but then that makes me wonder about the purpose of the function... – jscs Apr 25 '16 at 20:50
3

You should not do class comparisons with == as suggested in one of the other answers, unless you specifically want to test if the type of the object tested should exactly match the expected class and it is not allowed to be a subclass of the tested class.

You can use the instance method isKindOfClass to accomplish this, taking subclassing into account. See below for a code example.

NOTE: You may be surprised that this works on a pure Swift class type, given an instance method with the same name exists in NSObject / NSObjectProtocol. It does indeed work in pure Swift (as shown with the example code below – tested with Xcode 7.3, Swift 2.2.1), with no @objc types involved, as long as you import Foundation. I am presuming based on this that this instance method is added as an extension method in Foundation to all class types.

import Foundation

class A { }
class B { }
class C: B { }

enum ValueTestError: ErrorType {
    case UnexpectedClass(AnyClass)
}

func testInputValue(inputObj:AnyObject, isKindOfClass expectedClass:AnyClass) throws {
    guard inputObj.isKindOfClass(expectedClass) else {
        throw ValueTestError.UnexpectedClass(inputObj.dynamicType)
    }
}

let a = A()
let b = B()
let c = C()

do {
    try testInputValue(c, isKindOfClass: B.self)
} catch {
    print("This does not get printed as c is of type B (C is subclass of B).")
}

do {
    try testInputValue(a, isKindOfClass: B.self)
} catch {
    print("This gets printed as a is not of type B.")
}

Also, importantly although isKindOfClass is available as an instance method on AnyObject, trying to call it on an arbitrary Swift class typed object will only work if you first cast that object to AnyObject (which will always of course succeed). Example code illustrating this point is presented below, and there's more on this question over here.

import Foundation

class A {}
let a = A()

// the following compiles and returns 'true'
(a as AnyObject).isKindOfClass(A.self)

// the following fails to compile with "Value of type 'A' has no member 'isKindOfClass'"
a.isKindOfClass(A.self)
Community
  • 1
  • 1
mz2
  • 4,672
  • 1
  • 27
  • 47
  • Unless I'm missing something, you can only use `isKindOfClass` on `NSObject`s (or an object that conforms to `NSObjectProtocol`), not Swift classes. – JAL Apr 25 '16 at 21:33
  • That's what I thought too, hence also addressed that concern in my answer. It does work – there are only pure Swift types involved in my example which compiles and runs fine. – mz2 Apr 25 '16 at 21:35
  • `this method is added as an extension in Foundation to all class type objects as an instance method` can you show me where this extension exists in the stdlib? – JAL Apr 25 '16 at 21:36
  • As I note in my answer, it is an extension method added in Foundation that works for pure Swift class types (I know, I was surprised too). It is not in stdlib, it is in Foundation. – mz2 Apr 25 '16 at 21:39
  • Sorry, I meant Foundation. In which module does this extension exist? (looking at Foundation now, I don't see it) "Jump to Definition" seems to only work for `isKindOfClass` when it is being performed on an `NSObject`. – JAL Apr 25 '16 at 21:41
  • I don't know more than that this works when I do `import Foundation` and it doesn't when I don't import it. The symbol lookups are unreliable (at least with Xcode 7.3 and the corresponding Swift version) so I didn't go deeper than checking whether Cmd+clicking the symbol would bring me to the declaration (did not). What mattered to me more was that it actually works :-) – mz2 Apr 25 '16 at 21:44
  • I have clarified my answer to more clearly dispel the concern about this working / not working for pure Swift class types, and worded also it being an extension method in Foundation as an assumption (I can't think of what else it would be). – mz2 Apr 25 '16 at 21:53
  • Ok, so it looks like this only works if a Swift class is casted as `AnyObject`. I've asked a new question about `AnyObject` conforming to `NSObjectProtocol` [here](http://stackoverflow.com/q/36851866/2415822) if you're interested. – JAL Apr 25 '16 at 22:00
  • Interesting – very strange indeed that this works in the first place. I can't work out based on a brief reading of the Swift 3 source code even why it does indeed work in the first place. I'll also clarify in my answer your observation that this method is only present when cast to AnyObject. – mz2 Apr 25 '16 at 22:59
1

Not the greatest answer ever, but comparing with inputValue.dynamicType works:

if inputValue.dynamicType == expectedClass {
    print("inputValue.dynamicType \(inputValue.dynamicType) equals expectedClass \(expectedClass)")
    // ...
}
JAL
  • 41,701
  • 23
  • 172
  • 300