Is it possible to replicate Swifts automatic numeric value bridging to Foundation (NSNumber) for (U)Int8/16/32/64 types?
Question
- Is it possible to replicate Swifts numeric value bridging to Foundation:s
NSNumber
reference type, for e.g.Int32
,UInt32
,Int64
andUInt64
types? Specifically, replicating the automatic by-assignment bridging covered below.
Intended example usage of such a solution:
let foo : Int64 = 42
let bar : NSNumber = foo
/* Currently, as expected, error:
cannot convert value of type 'Int64' to specified type 'NSNumber */
Background
Some of the native Swift number (value) types can be automatically bridged to NSNumber
(reference) type:
Instances of the Swift numeric structure types, such as
Int
,UInt
,Float
,Double
, andBool
, cannot be represented by theAnyObject
type, becauseAnyObject
only represents instances of a class type. However, when bridging toFoundation
is enabled, Swift numeric values can be assigned to constants and variables ofAnyObject
type as bridged instances of theNSNumber
class....
Swift automatically bridges certain native number types, such as
Int
andFloat
, toNSNumber
. This bridging lets you create anNSNumber
from one of these types:let n = 42 let m: NSNumber = n
It also allows you to pass a value of type
Int
, for example, to an argument expecting anNSNumber
. ...All of the following types are automatically bridged to NSNumber:
- Int - UInt - Float - Double - Bool
From Interoperability - Working with Cocoa Data Types - Numbers.
So why attempt to replicate this for the IntXX
/UIntXX
types?
Primarily: Curiosity, sparked by seeing some questions recently with underlying problems covering confusion over why an Int
value type seemingly can be represented by an AnyObject
(reference) variable, whereas e.g. Int64
, cannot; which is naturally explained by the bridging covered above. To pick a few:
- Why is a Swift Array<Int> compatible with AnyObject?
- Cannot subscript a value of type '[UInt32]'
- Using generic arrays in swift
None of Q&A:s above mentions, however, the possibility of actually implementing such automatic bridging to AnyObject
(NSNumber
) from the non-bridged types Int64
, UInt16
and so on. The answers in these threads rather focuses (correctly) on explaining why AnyObject
cannot hold value types, and how the IntXX
/UIntXX
types are not bridged for automatic conversion to the underlying Foundation types of the former.
Secondarily: For applications running at both 32-bit and 64-bit architectures, there are some narrow use cases—using Swift native number types implicitly converted to AnyObject
, in some context—where using e.g. Int32
or Int64
type would be preferred over Int
. One (somewhat) such example:
- Why does this random function code crash on an iPhone 5 and 5S.
Yes (it's possible): by conformance to protocol _ObjectiveCBridgeable
(The following answer is based on using Swift 2.2 and XCode 7.3.)
Just as I was pondering over whether to post or simply skip this question, I stumbled over swift/stdlib/public/core/BridgeObjectiveC.swift
in the Swift source code, specifically the protocol _ObjectiveCBridgeable
. I've briefly noticed the protocol previously at Swiftdoc.org, but in its current (empty) blueprint form in the latter, I've never given much thought to it. Using the blueprints for _ObjectiveCBridgeable
from the Swift source we can, however, swiftly let some native of custom type conform to it.
Before proceeding, note that _ObjectiveCBridgeable
is an internal/hidden protocol (_UnderScorePreFixedProtocol
), so solutions based on it might break without warning in upcoming Swift versions.
Enabling Int64
bridging to Foundation class NSNumber
As an example, extend Int64
to conform to _ObjectiveCBridgeable
, and subsequently test if this quite simple fix is sufficient for the implicit type conversion (bridging) from Int64
to Foundation class NSNumber
holds.
import Foundation
extension Int64: _ObjectiveCBridgeable {
public typealias _ObjectiveCType = NSNumber
public static func _isBridgedToObjectiveC() -> Bool {
return true
}
public static func _getObjectiveCType() -> Any.Type {
return _ObjectiveCType.self
}
public func _bridgeToObjectiveC() -> _ObjectiveCType {
return NSNumber(longLong: self)
}
public static func _forceBridgeFromObjectiveC(source: _ObjectiveCType, inout result: Int64?) {
result = source.longLongValue
}
public static func _conditionallyBridgeFromObjectiveC(source: _ObjectiveCType, inout result: Int64?) -> Bool {
self._forceBridgeFromObjectiveC(source, result: &result)
return true
}
}
Test:
/* Test case: scalar */
let fooInt: Int = 42
let fooInt64: Int64 = 42
var fooAnyObj : AnyObject
fooAnyObj = fooInt // OK, natively
fooAnyObj = fooInt64 // OK! _ObjectiveCBridgeable conformance successful
/* Test case: array */
let fooIntArr: [Int] = [42, 23]
let fooInt64Arr: [Int64] = [42, 23]
var fooAnyObjArr : [AnyObject]
fooAnyObjArr = fooIntArr // OK, natively
fooAnyObjArr = fooInt64Arr // OK! _ObjectiveCBridgeable conformance successful
Hence, conformance to _ObjectiveCBridgeable
is indeed sufficient to enable automatic by-assignment bridging to the corresponding Foundation class; in this case, NSNumber
(in Swift, __NSCFNumber
).
Enabling Int8
, UInt8
, Int16
, UInt16
, Int32
, UInt32
, (Int64
), and UInt64
bridging to NSNumber
The above conformance of Int64
to _ObjectiveCBridgeable
can easily be modified to cover any of the Swift-native integer types, using the NSNumber
conversion table below.
/* NSNumber initializer: NSNumber native Swift type property
-------------------------------- -----------------------------------
init(char: <Int8>) .charValue
init(unsignedChar: <UInt8>) .unsignedCharValue
init(short: <Int16>) .shortValue
init(unsignedShort: <UInt16>) .unsignedShortValue
init(int: <Int32>) .intValue
init(unsignedInt: <UInt32>) .unsignedIntValue
init(longLong: <Int64>) .longLongValue
init(unsignedLongLong: <UInt64>) .unsignedLongLongValue */