ErYe
ErYe

Reputation: 53

A double number's type is Int in Swift

// Swift
import UIKit
let arr = [1, "a", 2.88]
let last = arr.last
if last is Int {
    print("The last is Int.")    // The last is Int.
} else {
    print("The last is not Int.")
}

I can't understand the result printed.
Why it print "The last is Int".

// Swift
import UIKit
let arr = [1, "a", -2]
let last = arr.last
if last is Double {
    print("The last is Double.")    // The last is Double.
} else {
    print("The last is not Double.")
}

And this print "The last is Double".Why?
Could somebody can help me?
Than you vary much.

Upvotes: 4

Views: 264

Answers (4)

vacawama
vacawama

Reputation: 154731

Swift arrays can only hold one type. When you declared:

let arr = [1, "a", 2.88]

Swift made arr of type [NSObject]. You can verify this by Option-clicking on arr to see its type. This only works because you have Foundation imported (your import UIKit imports Foundation as well). Try removing import UIKit.

Then, the values 1 and 2.88 were converted to NSNumber and "a" to NSString so that they can be stored in that [NSObject] array because Ints, Strings, and Doubles are not NSObjects. NSNumber and NSString are subclasses of NSObject. Swift picks the most restrictive type for the array. Had your array been [1, true, 2.88], the array type would have been [NSNumber].

The interesting thing about an NSNumber is that it is an object container that wraps many different types. You can put an Int in and take out a Double. So, it is misleading then when you test it with is. It responds "true" meaning, "I can be that if you want".


import Foundation

let n: NSNumber = 3.14

print(n is Int)       // "true"
print(n is Double)    // "true"
print(n is Bool)      // "true"

print(n as! Int)      // "3"
print(n as! Double)   // "3.14"
print(n as! Bool)     // "true"

Note: Had you declared your arr to be [Any], then this would have worked as you expected:

let arr:[Any] = [1, "a", 2.88]
let last = arr.last
if last is Int {
    print("The last is Int.")
} else {
    print("The last is not Int.")  // "The last is not Int."
}

But Swift is not going to create an array of type Any for you unless you ask explicitly (because quite frankly it is an abomination in a strongly typed language). You should probably rethink your design if you find yourself using [Any].

The only reason Swift creates [NSObject] when Foundation is imported is to make our lives easier when calling Cocoa and Cocoa Touch APIs. If such an API requires an NSArray to be passed, you can send [1, "a", 2.88] instead of [NSNumber(integer: 1), NSString(string: "a"), NSNumber(double: 2.88)].

Upvotes: 10

mz2
mz2

Reputation: 4702

When you have imported Foundation.framework you inherit a certain magic behaviour with numbers: if you create array literal with numerical literals that can't be represented with an array of that value type, an array is created that wraps the numbers boxed in a NSNumber. If you don't import Foundation (i.e. you rely on just the Swift standard library), then you in fact can't declare the array in such cases just with let arr = [1, "a", 2.88] (whereas for instance let arr = [1,2] would still work – that would produce an [Int]) but need to state let arr:[Any] = [1, "a", 2.88] – at which case no boxing to NSNumber happens (NSNumber is not even available) and the original type is somehow retained.

NSNumber is a box that can be used to wrap all manners of scalar (numerical) basic types, and you can use the as or is operator in Swift to successfully treat any NSNumber as any of the numerical types it can wrap. For instance the following holds:

var a = NSNumber(bool: true)
print("\(a is Bool)")   // prints "true"
print("\(a is Int)")    // prints "true"
print("\(a is Double)") // prints "true"
print("\(a is Float)")  // prints "true"

It is possible to figure out the kind of numerical type you used sometimes, but it's just not exposed for some reason to NSNumber but is only available in the corresponding CoreFoundation type CFNumber:

extension NSNumber {
    var isBoolean:Bool {
        return CFNumberGetType(self as CFNumber) == CFNumberType.CharType
    }

    var isFloatingPoint:Bool {
        return CFNumberIsFloatType(self as CFNumber)
    }

    var isIntegral:Bool {
        return CFNumberGetType(self as CFNumber).isIntegral
    }
}

extension CFNumberType {
    var isIntegral:Bool {
        let raw = self.rawValue
        return (raw >= CFNumberType.SInt8Type.rawValue && raw <= CFNumberType.SInt64Type.rawValue)
                || raw == CFNumberType.NSIntegerType.rawValue
                || raw == CFNumberType.LongType.rawValue
                || raw == CFNumberType.LongLongType.rawValue
    }
}

You can read more on this topic over here – there are some caveats.

Note also that the reference documentation also states the following:

… number objects do not necessarily preserve the type they are created with …

.

Upvotes: 0

Tim Vermeulen
Tim Vermeulen

Reputation: 12562

You can declare the array with type [Any] to get the desired result:

let arr: [Any] = [1, "a", 2.88]
arr.last is Int // false

Upvotes: 0

Twitter khuong291
Twitter khuong291

Reputation: 11700

The problem is your arr, if you declare

let arr = [1, "a", 2.88] // arr is [NSObject]

so

let arr = [1, "a", 2.88]
let last = arr.last // last is a NSObject?
if last is Int { // is Int or is Double will get the same result
    print("The last is Int.") // because the number of NSNumber is larger than the number of NSString
} else {
    print("The last is not Int.")
}

if you declare like this:

let arr = [1, "a", "b"]
let last = arr.last
if last is Int {
    print("The last is Int.")   
} else {
    print("The last is not Int.") // Will print because the number of NSString is larger than NSNumber.
}

Upvotes: 0

Related Questions