Downcasting collections

I'm a bit confused about what happens when downcasting NS… collections. I have this:


let jObject = try NSJSONSerialization.JSONObjectWithData (jsonData, options: NSJSONReadingOptions ())


which in this context is required to be a dictionary, so this is the way to downcast:


guard let jDictionary = jObject as? [String: AnyObject] else { return }


but I need to end up with a '[String: Any]' of "pure" Swift types, including Ints:


let properties: [String: Any] = jDictionary


What confuses me is that the values in jDictionary are AnyObject, so (I'd assume) any NSNumber values in the input stay as NSNumbers in jDictionary. But then, how do I get them bridged to Int in 'properties'? Do I have to iterate through the dictionary to force the bridging? Or are they automatically bridged in the last assignment?


Edit: The above compiled. Now that I try and run it, I get a crash with this message:


fatal error: can't unsafeBitCast between types of different sizes


which I don't exactly understand.

I'm not sure what exactly you are asking.

But you will never be able to cast an Array<X> to an Array<Any> (unless X is Any).

Array<X> is one type, and Array<Any> is a totally different type. Both these different types happen to have been generated by the compiler from a common "generic type" Array, which is not at all a type in itself, ie it is not a ("concrete") type unless its T is specified (by you, in your source code) or inferred, to allow the compiler to generate a type for that composition of the Array-thing and that specific T.

Both types does however also conform to eg CollectionType and SequenceType, but both of those protocols have Self requirements or associated types, so you can't use them for types or check conformance to them. So the following will not work once your Array<X> has been turned into an Any:

func isCollectionType<T: CollectionType>(v: T) -> Bool { return true }
func isCollectionType<T>(v: T) -> Bool { return false }

If you are intereseted in if something that has been turned into an Any is an Array or not then you can do something like this:

struct X {
    let v = 1234
}

protocol MyProtocolThatOnlyArrayTypesShouldConformTo {}
extension Array: MyProtocolThatOnlyArrayShouldConformTo {}

func isAnythingAnArray(v: Any) {
    if v is MyProtocolThatOnlyArrayTypesShouldConformTo {
        print("This is an array!")
    } else {
        print("This is not an array ...")
    }
}

let x: [X] = []

isAnythingAnArray(x)


Perhaps there is a better way.

The issue was that I assumed that Int was not AnyObject, but it is:


1 is AnyObject // true, no warning
(1 as! AnyObject).dynamicType // __NSCFNumber.Type
(1 as AnyObject).dynamicType // error: 'Int' is not convertible to 'AnyObject'; did you mean to use 'as!' to force downcast?
1.dynamicType

class X {}

X () is AnyObject // warning: 'is' test is always true
X () as AnyObject
(X () as AnyObject).dynamicType // X.Type


That's with NO import Foundation.


I thought AnyObject meant any class type, or reference type, and that these were all the same thing. In fact, AFAICT, Int implicitly conforms to AnyObject.


But it's weirder than that. Int to AnyObject is a downcast, but class X to AnyObject is an upcast. This make it hard to reason about types in, say, collections, where there might be Int values as well as class instance values.

When you type a literal value like '1' (or '2.5' or "aaa") into your code, there is an intermediate step before it becomes a Swift native type like Int (or Double, or String).


The '1' in your examples above isn't automatically an Int instance, and only exists as an IntegerLiteral value which the Swift compiler then tries to fit to some type that will allow your code to compile.


If you assign an IntegerLiteral value to a variable without an explicit type, the default type is Int; but if you try to do something with that IntegerLiteral value instead of assigning it to a variable, the Swift compiler will look for any type that can be created with an IntegerLiteral value which also works with whatever code you are calling with it.


Sometimes it comes up with very interesting interpretations (when Int is not a type which would allow the code to compile), rather than the Int type that you might expect.


So an Int does not conform to AnyObject; you are seeing an non-explicitly-typed IntegerLiteral value being converted to some type that does conform to AnyObject.

Well, it was a plausible theory, until I tried it:


let i: Int = 1

i is AnyObject // true, no warning
(i as! AnyObject).dynamicType // __NSCFNumber.Type
(i as AnyObject).dynamicType // error: 'Int' is not convertible to 'AnyObject'; did you mean to use 'as!' to force downcast?
i.dynamicType // Int.Type


IOW, exactly the same results as using a constant. This is consistent with the actual code I've been writing all day, where code like the following seems to match Int and String values from any source just fine:


init? (inValue: AnyObject) {
     switch inValue {
     case let value as Int:
          …
     case let value as String:
          …
     …

Are you really sure that you are not importing Foundation (or UIKit or Cocoa or anything else that includes Foundations)?

Because this is what I get (Xcode 7 beta 3):


Here's in a "pure Swift context":

// import Foundation // NOTE: No imports at all.
let x: Int = 1234
print(x is AnyObject) // false


With Foundation (and thus (the confusing) bridging):

import Foundation
let x: Int = 1234
print(x is AnyObject) // Warning 'is' test is always true

I was using a playground with NO imports. If I added 'import Foundation' then I got different results — in particular 'i is AnyObject' then acquired the same "is always true" warning.


However, if I run Swift from the command line, I get the same results you did, in both tests.

This automatic bridging thing is a mess. I checked in a playground and get the same result as you there. So we have different behaviour depending on if we import Foundation or not, and if we are in a playground or in an Xcode project.


Now I had to start this thread: https://forums.developer.apple.com/thread/8894

Downcasting collections
 
 
Q