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

1

7

Question

  • Is it possible to replicate Swifts numeric value bridging to Foundation:s NSNumber reference type, for e.g. Int32, UInt32, Int64 and UInt64 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, 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

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:

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:

Bullpup answered 9/3, 2016 at 13:52 Comment(2)
As of Swift 3, the fixed-sized integer types are bridged to NSNumber (github.com/apple/swift-evolution/blob/master/proposals/…, github.com/apple/swift-evolution/blob/master/proposals/…).Immanent
@MartinR Alas, Swift's progress swiftly eats up these old hacky Q&A:s! Thanks for the prompt, will try to add some short note after work.Bullpup
B
12

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              */
Bullpup answered 9/3, 2016 at 13:52 Comment(7)
You answer is almost perfectly comprehensive. But, as OP asks: why attempt to replicate this for the IntXX/UIntXX types? I don't see any advantage over directly use NSNumber constructor (initializer), which is available for every single Swift's 'number' type. For me the opposite is true, I hate 'free' automatic bridging. It is very easy to make a bug, because which value is used from NSNumber (as a container) is totally depend on programmer's wish. (no 'type-safe' checking more).Udder
@Udder Note that I am the OP myself and that I answered my own question here :) Thanks for your feedback: although your comment is valuable in the discussion of ones preference to auto. bridging (w.r.t. using NSNumber constructors directly), this Q&A mainly covers the technical aspects of auto. bridging, for those who are curious as well as those that prefer making use of the latter. But your point holds generally for most Swift<->obj-C interoperability: we take a step away from the strong type safety of "truly" native Swift code, and if not taking care, we might introduce bugs.Bullpup
:-) You answer is almost perfectly comprehensive, and OP must be full satisfied!Udder
But don't you think it's a bug that Apple doesn't just provide automatic bridging of these other numeric types? I do, and I have filed that bug.Diuretic
@Diuretic I agree that it's weird that we don't get this for free for the other numeric types. At the very least, all types (or none) conforming to IntegerType should have this bridging by default. Good thing that you've flagged this.Bullpup
BTW you can delete those three occurrences of @warn_unused_result from your code - they are not needed.Diuretic
@Diuretic I see, I just kept them as a remnant from the Swift source that I got the template from. Will remove, thanks.Bullpup

© 2022 - 2024 — McMap. All rights reserved.