Last active
April 25, 2016 19:37
-
-
Save azinman/f673780201adc8dff138de730dfaa6aa to your computer and use it in GitHub Desktop.
Some non-obvious behavior casting values
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var outputTarget = 8 | |
// AnyObject forces the Swift Int to become an NSNumber. NSNumbers can cast | |
// to various primitive types even if the original value is not of the same | |
// type. For example, ints can be cast to bool (objective-c bools are | |
// actually 8-bit chars), and the bool is true if the value is non-zero (can | |
// be 8, -100, etc). | |
func get(key: String) -> AnyObject? { | |
return outputTarget | |
} | |
func get<T>(key: String, inout target:T?) { | |
let value = outputTarget | |
print(value.dynamicType) // Int | |
target = value as? T | |
} | |
var isRed: Bool? | |
get("isRed", target: &isRed) | |
print(isRed) // nil since Swift primitives don't cast into other types | |
print(get("isRed") as? Bool) // Optional(true) because of NSNumber | |
print(get("isRed").dynamicType) // Optional<AnyObject> | |
outputTarget = 0 | |
get("isRed", target: &isRed) | |
print(isRed) // nil since Swift primitives don't cast into other types | |
print(get("isRed") as? Bool) // Optional(false) because of NSNumber | |
let someNSNumber:NSNumber = 8 | |
print(someNSNumber as? Bool) // Optional(true) | |
print(someNSNumber as? Float) // Optional(8.0) | |
// Set outputTarget above to this: var outputTarget:AnyObject = "not a bool" | |
//get("isRed", target: &isRed1) | |
//print(isRed1) // nil | |
//print(get("isRed") as? Bool) // nil | |
let anInt = 0 | |
print(anInt as? Int16) // nil because unrelated types in Swift | |
let kCastError = NSError.init(domain: "Json.Demo", code: 1, userInfo: nil) | |
extension NSNumber { | |
/// Returns true if value can be cast to NSNumber or NSNumber? | |
static func isNSNumber<V>(value:V) -> Bool { | |
// Can't do this: | |
// if value is NSNumber { | |
// because it will always succeed through type coercion, even if value is an Int. | |
// Instead we use the dynamic (runtime) type | |
let type = value.dynamicType | |
return type is NSNumber.Type || type is NSNumber?.Type | |
} | |
/// Returns true if target can be set/cast from this NSNumber without precision-loss or type | |
/// conversion. For example ```NSNumber.init(bool: true) as Float``` will return a Swift Float of | |
/// value 1, where as this function would return false as the types are unrelated -- only | |
/// casting as Bool would return true. This function checks for 32/64-bit correctness with types. | |
/// | |
/// Caveat: Currently this function makes no attempt to determine signed/unsigned correctness of | |
/// the underlying data, although this is sometimes knowable with NSNumber.objCType. | |
func isTargetCastable<T>(inout target:T?) -> Bool { | |
// Allow matching types of this size, and bigger. Signed and unsigned of same size are not allowed. | |
// Swift: https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html | |
// C types: https://developer.apple.com/library/ios/documentation/General/Conceptual/CocoaTouch64BitGuide/Major64-BitChanges/Major64-BitChanges.html | |
switch CFNumberGetType(self as CFNumberRef) { | |
// Obj-C bool is stored as Char, but if it's a @(YES) or @(NO) they are all a shared instance. | |
case .CharType where (unsafeAddressOf(self) == unsafeAddressOf(kCFBooleanFalse) || | |
unsafeAddressOf(self) == unsafeAddressOf(kCFBooleanTrue)): | |
// We know we have a bool... make sure it's compatible | |
let type = target.dynamicType | |
guard type == Bool?.self || type == NSNumber?.self || type == AnyObject?.self || type == NSObject?.self else { | |
return false | |
} | |
// Handle fixed lengths on 32/64 bit | |
case .SInt8Type, .CharType: | |
let type = target.dynamicType | |
guard type == Int?.self || type == UInt?.self || | |
type == Int8?.self || type == Int16?.self || type == Int32?.self || type == Int64?.self || | |
type == UInt8?.self || type == UInt16?.self || type == UInt32?.self || type == UInt64?.self || | |
type == CChar?.self || type == CShort?.self || type == CInt?.self || type == CLong?.self || type == CLongLong?.self || | |
type == NSNumber?.self || type == AnyObject?.self || type == NSObject?.self else { | |
return false | |
} | |
case .SInt16Type, .ShortType: | |
let type = target.dynamicType | |
guard type == Int?.self || type == UInt?.self || | |
type == Int16?.self || type == Int32?.self || type == Int64?.self || | |
type == UInt16?.self || type == UInt32?.self || type == UInt64?.self || | |
type == CShort?.self || type == CInt?.self || type == CLong?.self || type == CLongLong?.self || | |
type == NSNumber?.self || type == AnyObject?.self || type == NSObject?.self else { | |
return false | |
} | |
case .SInt32Type, .IntType: | |
let type = target.dynamicType | |
guard type == Int?.self || type == UInt?.self || | |
type == Int32?.self || type == Int64?.self || | |
type == UInt32?.self || type == UInt64?.self || | |
type == CInt?.self || type == CLong?.self || type == CLongLong?.self || | |
type == NSNumber?.self || type == AnyObject?.self || type == NSObject?.self else { | |
return false | |
} | |
case .SInt64Type, .LongLongType: | |
let type = target.dynamicType | |
guard (type == Int?.self && sizeof(Int) == sizeof(CLongLong)) || | |
(type == UInt?.self && sizeof(UInt) == sizeof(CLongLong)) || | |
type == Int64?.self || type == UInt64?.self || | |
type == CLongLong?.self || | |
type == NSNumber?.self || type == AnyObject?.self || type == NSObject?.self else { | |
return false | |
} | |
case .Float32Type, .FloatType: | |
let type = target.dynamicType | |
guard type == Float?.self || | |
type == Double?.self || | |
type == Float32?.self || | |
type == Float64?.self || | |
type == CFloat?.self || type == CDouble?.self || type == CGFloat?.self || | |
type == NSNumber?.self || type == AnyObject?.self || type == NSObject?.self else { | |
return false | |
} | |
case .Float64Type, .DoubleType: /* 64-bit IEEE 754 */ | |
let type = target.dynamicType | |
guard type == Double?.self || | |
type == Float64?.self || | |
type == CDouble?.self || type == CGFloat?.self || | |
type == NSNumber?.self || type == AnyObject?.self || type == NSObject?.self else { | |
return false | |
} | |
// Handle 32/64-bit types | |
case .LongType, .NSIntegerType: | |
let type = target.dynamicType | |
guard type == Int?.self || type == UInt?.self || | |
(type == Int32?.self && sizeof(Int32) == sizeof(NSInteger)) || | |
(type == UInt32?.self && sizeof(UInt32) == sizeof(NSInteger)) || | |
type == Int64?.self || type == UInt64?.self || | |
type == NSNumber?.self || type == AnyObject?.self || type == NSObject?.self || | |
type == CLong?.self || type == CLongLong?.self else { | |
return false | |
} | |
case .CGFloatType: | |
let type = target.dynamicType | |
guard (type == Float?.self && sizeof(CGFloat) == sizeof(Float)) || | |
type == Double?.self || | |
(type == Float32?.self && sizeof(CGFloat) == sizeof(Float32)) || | |
type == Float64?.self || | |
type == NSNumber?.self || type == AnyObject?.self || type == NSObject?.self else { | |
return false | |
} | |
// Misc | |
case .CFIndexType: | |
guard target.dynamicType == CFIndex?.self else { | |
return false | |
} | |
} | |
return true | |
} | |
} | |
var outputTarget2:AnyObject = 8 | |
func restrictiveGet<T>(key: String, inout target:T?) throws { | |
let value = outputTarget2 | |
if NSNumber.isNSNumber(value) { | |
guard (value as! NSNumber).isTargetCastable(&target) else { | |
print("value isnt' an nsnumber") | |
throw kCastError | |
} | |
} | |
target = value as? T | |
} | |
do { | |
try restrictiveGet("isRed", target: &isRed) | |
print(isRed) | |
} catch _ { | |
print("Couldn't do it") // Couldn't do it | |
} | |
outputTarget2 = true | |
do { | |
try restrictiveGet("isRed", target: &isRed) | |
print(isRed) // Optional(true) | |
} catch _ { | |
print("Never gets here") | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The ramifications are this are annoying for JSON decoding with NSJSONSerialization. It means that unrelated types can be accidentally and silently cast to each other when things are decoded using AnyObject, and mitigating that requires more complicated run-time checks if desired.