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

Posted

技术标签:

【中文标题】是否可以为 (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? 【发布时间】:2016-03-09 13:52:27 【问题描述】:

问题

是否可以将 Swift 的数值桥接到 Foundation:s NSNumber 引用类型,例如Int32UInt32Int64UInt64 类型?具体来说,复制下面介绍的自动分配桥接。

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

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

背景

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

Swift 数值结构类型的实例,例如IntUIntFloatDoubleBool,不能用 AnyObject 类型,因为 AnyObject 仅表示 a 的实例 类类型。然而,当桥接到Foundation 被启用时,Swift 数值可以分配给常量和变量 AnyObject 类型为 NSNumber 类的桥接实例

...

Swift 自动桥接某些本机数字类型,例如 IntFloat,到NSNumber。这种桥接使您可以创建一个 NSNumber 来自这些类型之一:

let n = 42
let m: NSNumber = n

它还允许您将Int 类型的值,例如,传递给 期望 NSNumber 的参数。 ...

以下所有类型都会自动桥接到 NSNumber:

- Int
- UInt
- Float
- Double
- Bool

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

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

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

Why is a Swift Array<Int> compatible with AnyObject? Cannot subscript a value of type '[UInt32]' Using generic arrays in swift

然而,上面的问答都没有提到从非桥接类型Int64UInt16 等实际实现这种自动桥接到AnyObject (NSNumber) 的可能性。这些线程中的答案更侧重于(正确地)解释为什么 AnyObject 不能保存值类型,以及如何不桥接 IntXX/UIntXX 类型以自动转换为前者的基础 Foundation 类型。

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

Why does this random function code crash on an iPhone 5 and 5S.

【问题讨论】:

从 Swift 3 开始,固定大小的整数类型桥接到 NSNumber (github.com/apple/swift-evolution/blob/master/proposals/…, github.com/apple/swift-evolution/blob/master/proposals/…)。 @MartinR 唉,Swift 的进步迅速吞噬了这些老生常谈的问答!感谢您的提示,下班后会尝试添加一些简短的注释。 【参考方案1】:

是的(有可能):符合协议_ObjectiveCBridgeable

(以下答案基于使用 Swift 2.2 和 XCode 7.3。)

就在我考虑是发布还是直接跳过这个问题时,我偶然发现了 Swift 源代码中的swift/stdlib/public/core/BridgeObjectiveC.swift,特别是协议_ObjectiveCBridgeable。我之前在Swiftdoc.org 曾短暂地注意到该协议,但在后者的当前(空)蓝图形式中,我从未考虑过太多。但是,使用来自 Swift 源代码的 _ObjectiveCBridgeable 的蓝图,我们可以迅速让一些原生的自定义类型符合它。

在继续之前,请注意 _ObjectiveCBridgeable 是一个内部/隐藏协议 (_UnderScorePreFixedProtocol),因此基于它的解决方案可能会在即将推出的 Swift 版本中中断而不会发出警告。


启用 Int64 桥接到 Foundation 类 NSNumber

例如,扩展Int64 以符合_ObjectiveCBridgeable,然后测试这个非常简单的修复是否足以实现从Int64 到基础类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 确实足以启用与相应Foundation 类的自动分配桥接;在这种情况下,NSNumber(在 Swift 中,__NSCFNumber)。


启用Int8UInt8Int16UInt16Int32UInt32、(Int64) 和UInt64 桥接至NSNumber

使用下面的NSNumber 转换表,可以轻松修改Int64_ObjectiveCBridgeable 的上述一致性以涵盖任何Swift 原生整数类型。

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

【讨论】:

您的回答几乎非常全面。但是,正如 OP 所问:为什么要尝试为 IntXX/UIntXX 类型复制这个?我看不出比直接使用 NSNumber 构造函数(初始化器)有任何优势,它适用于每个 Swift 的“数字”类型。对我来说恰恰相反,我讨厌“免费”自动桥接。很容易出现错误,因为从 NSNumber (作为容器)使用哪个值完全取决于程序员的意愿。 (没有“类型安全”检查更多)。 @user3441734 请注意,我自己是 OP,我在这里回答了我自己的问题 :) 感谢您的反馈:尽管您的评论在讨论 对汽车的偏好方面很有价值。桥接(w.r.t. 直接使用NSNumber 构造函数),本问答主要涵盖汽车的技术方面。桥接,适合那些好奇的人以及喜欢使用后者的人。但是您的观点对于大多数 Swiftobj-C 互操作性普遍适用:我们远离“真正的”原生 Swift 代码的强类型安全性,如果不注意,我们可能会引入错误。 :-) 你的回答几乎很全面,OP一定很满意! 但是你不认为Apple不只是提供这些其他数字类型的自动桥接是一个错误吗?我有,而且我已经提交了那个错误。 @matt 我同意我们没有为其他数字类型免费获得这个很奇怪。至少,所有符合IntegerType 的类型(或没有)默认情况下都应该有这个桥接。幸好您已对此进行了标记。

以上是关于是否可以为 (U)Int8/16/32/64 类型复制 Swifts 自动数值桥接到 Foundation (NSNumber)?的主要内容,如果未能解决你的问题,请参考以下文章

aspnet用户登录时怎么做区分是管理员还是普通用户?

Typescript(真的)是不是遵循泛型中参数化类型(T、U、V、W)的命名约定?

如何将字符串类型从 API 响应转换为图像文件 - ����\u0000\u0010JFIF\u0000\u0001\u0001\u0000\u0000\u0001 -

JS判断用户手机类型及是否是在微信客户端内部打开网页

必恩威 U盘 怎么量产

U盘测速工具那个最好用?