Swift type inference and type checking issue

2019-06-28 06:14发布

问题:

I'm not looking for an answer like how to do it correctly but why this happens.

Here is the code:

func isInt(param: AnyObject?) {
    if let value = param as? Int {
        print(value)
    } else {
        print("Not Int")
    }

    if let value = param {
        if value is Int {
            print(value)
        } else {
            print("Not Int")
        }
    }
}

let a:AnyObject? = 1.2
let b:Float? = 1.2
let c:Double? = 1.2

isInt(a)
isInt(b)
isInt(c)

I understand in the first if loop, the param is casted to Int and then print out 1.

But why in second if loop, if value is Int is true and then print out 1.2?

回答1:

In your b case, let value = param bridges value to an NSNumber type. For NSNumber, value is Int will always be true.

For unbridged values:

a is Int // always true, AnyObject bridges to NSNumber here
b is Int // false, cast from Float to Int always fails
c is Int // false, cast from Double to Int always fails

This answer assumes Foundation has been imported. Without Foundation, your assignments will fail.



回答2:

Take a look at https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html, specifically:

Instances of the Swift numeric structure types, such as Int, UInt, Float, Double, and Bool, cannot be represented by the AnyObject type, because AnyObject only represents instances of a class type. However, when bridging to Foundation is enabled, Swift numeric values can be assigned to constants and variables of AnyObject type as bridged instances of the NSNumber class.

We can see this in action in a Playground:

let value = 1.2
value.dynamicType                   //Double.Type
value is Int                        //false
let castValue = value as AnyObject
castValue.dynamicType               //__NSCFNumber.Type (a private framework class, part of the NSNumber class cluster)
//NSNumber is bridged to Int, UInt, Float, Double, and Bool so `is` tests for those types will return `true`
castValue is Int                    //true
castValue is Float                  //true
//NSNumber is not bridged to String so an `is` test will return `false`
castValue is String                 //false


回答3:

well, it seems that it if you do the following:

func isInt(param: AnyObject?) {

    if let value = param {
        if value is Double {
            print("I'm a double")
        }
    if value is Int{
            print("I'm an int")
        }else {
            print("Not Int")
        }
    }
}

it will print "I'm a double" for all three and "I'm an int" for all three as well. It seems that when it goes to the if statement it bridges value to NSNumber, which will be true for any NSNumber type.

However, let's say you do the following:

if let value = param {
        if value is String {
            print("I'm a String")
        } 
        if value is Int{
            print("I'm an int")
        }else {
            print("Not Int")
        }
    }
}

since String is not of type NSNumber, it will skip and go to the if value is Int, which is of type NSNumber and return the value.