是否可以为 (U)Int8/16/32/64 类型复制 Swifts 自动数值桥接到 Foundation (NSNumber)?

Is it possible to replicate Swifts automatic numeric value bridging to Foundation (NSNumber) for (U)Int8/16/32/64 types?

问题

此类解决方案的预期用法示例:

let foo : Int64 = 42
let bar : NSNumber = foo
    /* Currently, as expected, error:
       cannot convert value of type 'Int64' to specified type 'NSNumber */

背景

一些原生的Swift数字(值)类型可以自动桥接到NSNumber(参考)类型:

Instances of the Swift numeric structure types, such as Int, UInt, Float, Double, and Bool, cannot be represented by the AnyObject type, because AnyObject only represents instances of a class type. However, when bridging to Foundation is enabled, Swift numeric values can be assigned to constants and variables of AnyObject type as bridged instances of the NSNumber class.

...

Swift automatically bridges certain native number types, such as Int and Float, to NSNumber. This bridging lets you create an NSNumber 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 an NSNumber. ...

All of the following types are automatically bridged to NSNumber:

- Int
- UInt
- Float
- Double
- Bool

来自 Interoperability - Working with Cocoa Data Types - Numbers.

那么为什么要尝试为 IntXX/UIntXX 类型复制这个?

主要是: 好奇心,最近看到一些问题引发了一些潜在问题,这些问题包括对 Int 值类型的混淆似乎可以用 AnyObject (参考)变量表示,而例如Int64, 不能;这自然可以通过上面提到的桥接来解释。挑几个:

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 等等。这些线程中的答案(正确地)集中在解释为什么 AnyObject 不能保存值类型,以及 IntXX/UIntXX 类型如何不桥接以自动转换为底层基础类型前者。

其次: 对于 32 位和 64 位架构的应用程序 运行,有一些狭窄的用途案例——使用 Swift 本机数字类型隐式转换为 AnyObject,在某些情况下——使用例如Int32Int64 类型优于 Int。一个(有点)这样的例子:

是(可能):符合协议 _ObjectiveCBridgeable

(以下答案是基于使用Swift2.2和XCode7.3。)

正当我在考虑是 post 还是干脆跳过这个问题时,我偶然发现了 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,但在后者当前的(空)蓝图形式中,我从来没有深思熟虑。然而,使用来自 Swift 源的 _ObjectiveCBridgeable 的蓝图,我们可以迅速让一些本地自定义类型符合它。

在继续之前,请注意 _ObjectiveCBridgeable 是一个 internal/hidden 协议 (_UnderScorePreFixedProtocol),因此基于它的解决方案可能会在即将到来的 Swift 版本中毫无警告地中断。


启用 Int64 桥接到 Foundation class NSNumber

例如,扩展 Int64 以符合 _ObjectiveCBridgeable,然后测试这个非常简单的修复是否足以实现从 Int64 到 Foundation 的隐式类型转换(桥接) class NSNumber 成立。

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 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

因此,符合 _ObjectiveCBridgeable 确实足以启用自动按分配桥接到相应的基金会 class;在这种情况下,NSNumber(在 Swift、__NSCFNumber 中)。


启用Int8UInt8Int16UInt16Int32UInt32、(Int64),以及 UInt64 桥接到 NSNumber

上述 Int64_ObjectiveCBridgeable 的一致性可以很容易地修改以涵盖任何 Swift-native 整数类型,使用 NSNumber 转换 table 下面。

/* 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              */