Is a giant switch/case the right way to convert NSNumber to a generic type `T`?

I need to assign a NSNumber value to a @Binding var value: T, is this the right way?

extension NSNumber {
    /// Convert this `NSNumber` to a concrete type, used like this:
    /// ```
    /// @Binding var field: T           // T is known here
    /// field = anNSNumber.convert()    // so type can be inferred
    /// ```
    /// - Parameter type: the type to convert to, should automatically inferred from assignment on the left side
    /// - Returns: the value of this `NSNumber` in specific type T
    // 🤔 is there anyway here to constraint `T` to only what `NSNumber` can handle?
    //    I guess if this is possible, we wouldn't need this giant switch/case?
    func convert<T>(_ type: T.Type = T.self) -> T {
        switch type {
        case is Bool.Type:
            return boolValue as! T
        case is CChar.Type:
            return int8Value as! T
        case is Decimal.Type:
            return decimalValue as! T
        case is Double.Type:
            return doubleValue as! T
        case is Float.Type:
            return floatValue as! T
        case is Int32.Type:
            return int32Value as! T
        case is Int.Type:
            return intValue as! T
        case is Int64.Type:
            return int64Value as! T
        case is Int16.Type:
            return int16Value as! T
        case is UInt8.Type:
            return uint8Value as! T
        case is UInt.Type:
            return uintValue as! T
        case is UInt32.Type:
            return uint32Value as! T
        case is UInt64.Type:
            return uint64Value as! T
        case is UInt16.Type:
            return uint16Value as! T
        default:
            fatalError("Unknown type (\(type)), NSNumber is unable to convert to this type (:.")
        }
    }
}

Can you not use someNSNumber as? Int or whatever type you need? that seems much simpler and safer

Edit (2 :): so this seems to work:

    func convert<T>(_ type: T.Type = T.self) -> T {
        self as! T
    }

I didn't know you can cast NSNumber to something...

I need this convert for this:

struct AddDecimalAndDoneKeyboardToolbar<T>: ViewModifier {
...
}

So convert need to work with whatever T is that NSNumber can produce, that's in that giant switch/case.

See: How to convert a String to generic T, where T is unconstrainted? - #3 by young

It feels like perhaps you are attempting to use NSNumber in a manner that it doesn't serve. NSNumber is nearly an AnyNumeric box, its behavior is that it will store the most reasonable storage of what you put in but only allow safe casts out. Doing the accessors as you have listed is going to truncate the values in some cases. Say for example you store a UInt64 of 8710020202 (clearly larger than a CChar's storage size). If you ask that NSNumber for it's int8Value it will merrily give you 106. But if you ask that number to give the as CChar it will emit nil.

The edited version is closer, but I feel that is going to blow up if T is something other than a Bool or Numeric type.

Why not just do this?

func convert<T: Numeric>(_ type: T.Type = T.self) -> T? {
   self as? T
}
func convert(_ type: Bool.Type) -> Bool? {
   self as? Bool
}

:+1::pray::bowing_woman:t2: You solved my two problems:

  1. I only need to handle Numeric types, so <T: Numeric> is good enough
  2. The NSNumber comes from the Formatter of TextField, I got rid of the convert and just do cast directly, the as! T should be safe since T: Numeric?:
        @objc func doneAction(_ button: UIBarButtonItem) {
            field = (formatter.number(from: textField.text ?? "0") ?? NSNumber(value: 0)) as! T
            textField.resignFirstResponder()
        }

How does cast to any numeric work with NSNumber? Can I write my own type that can do this?

let d = 1.23
let I = d as! Int       // this will crash, so how does NSNumer do this?

as? and as! casting is restricted to bridgeable types only - it requires support partially from the compiler and partially from things that are available only to certain layers like the standard library or Foundation's overlay. I would suggest that the proper way above those layers is to use nullable initializers for conversion or nullable accessor methods/properties. Trying to shoehorn support for bridging out side of those types is a dark and torrid affair not recommended for the feint of heart or shipping apps ;)

1 Like

I re-read the NSNumber doc, it doesn't say NSNumber can be "casted" to Numeric and Bool type. Seems like a major omission for such special capability. Is this something well known, maybe in the ObjectiveC world or "Tool-Free Bridge"?