Cast Any to Float always fails in swift4.1

2020-04-03 16:15发布

问题:

In the former version, to get a float value from a [String: Any] dictionary, I can use let float = dict["somekey"] as? Float, but in swift4.1, it doesn't work. It seems the type of dict["somekey"] has been implicitly inferred as Double before I get it, so casting from Double to Float always fails. I wonder if it is a new characteristic or just a bug.

--Here is the update.

I re-downloadeded an Xcode9.2 and did some experiments, now I think I figure out what's going on. Here is the test code:

let dict: [String : Any] = ["key": 0.1]

if let float: Float = dict["key"] as? Float {
    print(float)
} else {
    print("nil")
}

let dict1: [String : Any] = ["key": NSNumber(value: 0.2)]

if let float: Float = dict1["key"] as? Float {
    print(float)
} else {
    print("nil")
}

let number = NSNumber(value: 0.3)
if let float: Float = number as? Float {
    print(float)
} else {
    print("nil")
}

let number1 = NSNumber(floatLiteral: 0.4)
if let float = number1 as? Float {
    print(float)
} else {
    print("nil")
}

Running this code in Playground of Swift4 and Swift4.1, the results are different. In Swift4, the results are nil 0.2 0.3 0.4, and In Swift4.1 the results are nil nil nil nil. From the result, I can learn two points: 1. When we convert JSON data into a [String : Any] dictionary with the JSONSerialization class, the numeric value is saved as an NSNumber object, but not Int, Double or Float. 2. In Swift4, we can use let float = NSNumberOjbect as? Float to get a Float value, but in Swift4.1 we can't. But still, we can get Int or Double value in this way, either in Swift4 or Swift4.1. Finally again, is this a new feature or a bug? If someone knows, can you guys show up the announcement link?

回答1:

You need to distinguish two cases (in Swift 3, it was three cases):

  • Any containing a Swift native Double
  • Any containing an NSNumber

(In Swift 3, there was type preserving NSNumber case other than the normal NSNumber.)


When you create a native Swift Dictionary such as [String: Any], and set Double value in a normal way like this in your update:

let dict: [String : Any] = ["key": 0.1]

In this case, Any holds the metadata representing Double and the raw value 0.1 as Double.

And casting Any holding Double to Float always fails. As Double to Float cannot be converted with as-castings.

let dblValue: Double = 0.1
if let fltValue = dblValue as? Float { //<-Cast from 'Double' to unrelated type 'Float' always fails
    print(fltValue)
} else {
    print("Cannot convert to Float")
}
//->Cannot convert to Float

But, in case of Any holding NSNumber, as always happens when the Array or Dictionary is bridged from NSArray or NSDictionary, the behaviors are different between former Swifts and Swift 4.1.

The result of JSONSerialization matches this case.

In former Swifts, (normal) NSNumber to Float was an always-success operation.

And in Swift 4.1, the behavior has changed with the new feature which I have shown in my comment:

SE-0170 NSNumber bridging and Numeric types


I omit the third case once found in Swift 3, it's past.

But it is very important how you get your Dictionary or Array, and how you set the value to them, to solve the issue Any to Float.


Finally again, is this a new feature or a bug? If someone knows, can you guys show up the announcement link?

This is a new feature, not a bug. See the link above, and related threads below.

Unable to bridge NSNumber to Float Swift 3.3

Unexpected behavior when casting an NSNumber to Float