Skip to content

Instantly share code, notes, and snippets.

@azinman
Last active April 25, 2016 19:37
Show Gist options
  • Save azinman/f673780201adc8dff138de730dfaa6aa to your computer and use it in GitHub Desktop.
Save azinman/f673780201adc8dff138de730dfaa6aa to your computer and use it in GitHub Desktop.
Some non-obvious behavior casting values
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")
}
@azinman
Copy link
Author

azinman commented Apr 22, 2016

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment