swift中is的小問題

在swift中用到is者個關(guān)鍵字,結(jié)果發(fā)現(xiàn)了其中一個莫名其妙的小問題

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.")
}

數(shù)組后面明明是一個Double類型,可是通過is判斷,打印出的結(jié)果卻是The last is Int.而且不論最后一個是Double或者是Int都會顯示同樣的結(jié)果.但是如果是String就不會顯示.
表示很納悶,請教了剛哥,剛哥也比較困惑,于是把這個問題放到了stackoverflow上.
沒多久得到一個教科書般的回復.

回復一

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)].

回復二

When you have imported Foundation.frameworkyou 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 statelet 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 …

結(jié)論

看了上面的回復,總算搞清楚了.像我上面的代碼,導入Foundation框架后創(chuàng)建出來的數(shù)組,1,2.88會自動包裝為NSNumber,而這個NSNumber會和Bool,Int,Double,Float匹配,所以返回true.

老外解釋的非常詳細,不止是提出了問題的原因所在,甚至給出了解決方案.
面對這樣的答案我只能說,路漫漫其修遠兮,態(tài)度很重要...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容