Reputation: 53
// 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
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 Int
s, String
s, and Double
s 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
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
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
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