How to compare Class type to instance type?

This post is somwhat related to a prior poston the old forums.


I have two dictionaries, and I want to verify that a few values in each are of the proper class, and after that then compare their values.

This compiles but fails:


  var looksTheSame = true
  let keys: [String: AnyClass] = ["modify_time": NSDate.self, "schedule_time": NSDate.self, "status": NSString.self] // String.self?
  for (key, value) in keys {
   print("OLD: \(key) old=\(oldInfo[key]) new=\(info[key])")
   guard
     let old = oldInfo[key] where old.dynamicType === value,
     let new = info[key] where ney.dynamicType === value
   else { looksTheSame = false; break }
   ... now I can compare objects
  }
print(looksTheSame) // FAILS


I tried a number of other attems to essentially grab some property of the class, then use that to compare it against the instance dynamicType, no no avail.


What I did find that works is to create a "key" dictionary with a random instance of the proper object (ie NSDate() or ""), and then compare object type to object type:


var looksTheSame = true
let keys: [String: AnyObject] = ["modify_time": NSDate(), "schedule_time": NSDate(), "status": ""]
for (key, value) in keys {
   guard
      let old = x[key] where old.dynamicType === value.dynamicType,
      let new = x[key] where new.dynamicType === value.dynamicType
   else { looksTheSame = false; break }
   ... now I can compare objects
}
print(looksTheSame) // SUCCESS


Is there a better way to do this?

Answered by OOPer in 68745022

With this code, I get the following:

let keys: [String: AnyClass] = ["modify_time": NSDate.self, "schedule_time": NSDate.self, "status": NSString.self]
for (key, value) in keys {
    print("OLD: \(key) old=\(oldInfo[key]) new=\(info[key])")
    print(value)
    //`guard` gets meaningless, just for experiment...
    print(oldInfo[key]!.dynamicType)
    print(info[key]!.dynamicType)
    guard
        let old = oldInfo[key] where old.dynamicType === value,
        let new = info[key] where new.dynamicType === value
        else { looksTheSame = false; break }
    //...
}

Output:

NSDate

__NSDate

__NSDate


It seems NSDate is implemented as sort of class cluster, and the actual type of your NSDate instance is not NSDate itself.

In fact, while I was editing the Playground file, the output changed like this:

NSDate

__NSDate

__NSTaggedDate


Which means, your latter code may fail if any of the NSDate instances have other types than the referece instance NSDate().

Maybe you need to use Objective-C runtime feature, if such runtime type checking is required.

let keys: [String: AnyClass] = ["modify_time": NSDate.self, "schedule_time": NSDate.self, "status": NSString.self]
for (key, value) in keys {
    print("OLD: \(key) old=\(oldInfo[key]) new=\(info[key])")
    guard
        let old = oldInfo[key] where (old as! NSObject).isKindOfClass(value),
        let new = info[key] where (new as! NSObject).isKindOfClass(value)
        else { looksTheSame = false; break }
    //...
}
print(looksTheSame)
Accepted Answer

With this code, I get the following:

let keys: [String: AnyClass] = ["modify_time": NSDate.self, "schedule_time": NSDate.self, "status": NSString.self]
for (key, value) in keys {
    print("OLD: \(key) old=\(oldInfo[key]) new=\(info[key])")
    print(value)
    //`guard` gets meaningless, just for experiment...
    print(oldInfo[key]!.dynamicType)
    print(info[key]!.dynamicType)
    guard
        let old = oldInfo[key] where old.dynamicType === value,
        let new = info[key] where new.dynamicType === value
        else { looksTheSame = false; break }
    //...
}

Output:

NSDate

__NSDate

__NSDate


It seems NSDate is implemented as sort of class cluster, and the actual type of your NSDate instance is not NSDate itself.

In fact, while I was editing the Playground file, the output changed like this:

NSDate

__NSDate

__NSTaggedDate


Which means, your latter code may fail if any of the NSDate instances have other types than the referece instance NSDate().

Maybe you need to use Objective-C runtime feature, if such runtime type checking is required.

let keys: [String: AnyClass] = ["modify_time": NSDate.self, "schedule_time": NSDate.self, "status": NSString.self]
for (key, value) in keys {
    print("OLD: \(key) old=\(oldInfo[key]) new=\(info[key])")
    guard
        let old = oldInfo[key] where (old as! NSObject).isKindOfClass(value),
        let new = info[key] where (new as! NSObject).isKindOfClass(value)
        else { looksTheSame = false; break }
    //...
}
print(looksTheSame)

Any reason why you want to check by hand instead of letting the type system do it's work? I mean the big advantage of Swift *is* that it's strongly static typed...

I don't understand your point. I'm getting this data as JSON, converting to native objects, but never know for sure what the server will send, and/or if it gets converted into some other object type than the one expected.


OOPers reply above is probably the best solution - I was trying to find an "all Swift" way to do it, but that is probably impossible.

While your answer appears to be the proper way to do things, I did find another solution, which is to filter my two lists by "value is type", where type is say NSDate or String. In this case I cannot use a variable to hold the type (it would appear), but since I only have a few types I can do is by "hardcoding" in the type:


let x: [String: AnyObject] = ["modify_time": NSDate(), "schedule_time": NSDate.distantFuture(), "status": "Howdie"]
let y: [String: AnyObject] = ["modify_time": NSDate(), "schedule_time": NSDate.distantPast(), "status": "Howdie" as NSString]
let keys: [String: AnyObject] = ["modify_time": NSDate(), "schedule_time": NSDate(), "status": ""]
let xx = x.filter { (s: String, o: AnyObject) in o is NSDate }
let yy = y.filter { (s: String, o: AnyObject) in o is String }
print(xx)
print(yy)

You have found a very suggestive article.


So, how is this?

let keys: [String: AnyObject->Bool] = ["modify_time": {$0 is NSDate}, "schedule_time": {$0 is NSDate}, "status": {$0 is String}]
for (key, predicate) in keys {
    print("OLD: \(key) old=\(oldInfo[key]) new=\(info[key])")
    guard
        let old = oldInfo[key] where predicate(old),
        let new = info[key] where predicate(new)
        else { looksTheSame = false; break }
    //...
}
print(looksTheSame) //->true

Well you could simply parse the JSON before just creating objects for everything send and only create objects ofr supported elements.

How to compare Class type to instance type?
 
 
Q