[Idea] Improved bridging of NSNumber and NSValue types


(Mike Kasianowicz) #1

Hello, I heard this is the place to talk about things we’d like to see in Swift.

There are a few improvements I’d like to see with Objective-C bridging. I find myself repeating the same three code patterns in order to have the best of both Objective-C and Swift worlds.

First, optional primitive types should map to optional NSNumber. Currently, they only map to NSNumber when used in an array or dictionary. The implicitly generated code would look like the following:

    var optIntProperty: Int?
    
    @objc(optIntProperty)
    var objc_optIntProperty: NSNumber? {
        get {
            guard let value = self.optIntProperty else {
                return nil
            }
            return NSNumber(integer: value)
        }
        set {
            guard let value = newValue else {
                self.optIntProperty = nil
                return
            }
            
            self.optIntProperty = value.integerValue
        }
    }

In the code above, you cannot mark the Int? property dynamic - it causes a compiler error - but it would be nice if you could. This feature would allow shared ObjC classes to be more Swifty, and enable existing runtime reflection/serialization code. I could also see utility of NSNull-nil bridging for values in collections, though this is a rarer case.

Second, it would be nice for C-style structs to bridge to NSValue by default. Currently, every C struct has to implement NSValue bridging manually. I think it’s safe to say that if you mark a struct array property as @objc or dynamic that you typically want it to bridge to NSValue. Additionally, the same optional handling as noted above would be useful.

// pretend this struct were declared in C, or if we get struct exporting in the future (?)
public struct MyCStyleStruct {
    public var intProp: Int
    public var doubleProp: Float
}

extension MyCStyleStruct : _ObjectiveCBridgeable {
    public typealias _ObjectiveCType = NSValue
    public static func _isBridgedToObjectiveC() -> Bool {
        return true
    }
    
    public static func _getObjectiveCType() -> Any.Type {
        return NSValue.self
    }
    
    public func _bridgeToObjectiveC() -> _ObjectiveCType {
        var sself = self
        // would need compiler to create the @encode()
        return NSValue(&sself, withObjCType: "{MyCStyleStruct=if}")
    }
    
    public static func _forceBridgeFromObjectiveC(source: _ObjectiveCType, inout result: MyCStyleStruct?) {
        source.getValue(&result)
    }
    
    public static func _conditionallyBridgeFromObjectiveC(source: _ObjectiveCType, inout result: MyCStyleStruct?) -> Bool {
        _forceBridgeFromObjectiveC(source, result: &result)
        return true
    }
}

Finally, consider automatic bridging of RawRepresentable types (enums) as well, making the _ObjectiveCBridgeable code implicit in the following:

public enum MyEnum: Int {
    case Zero = 0
    case One = 1
    case Two = 2
    case Three = 3
}

extension MyEnum : _ObjectiveCBridgeable {
    public typealias _ObjectiveCType = NSNumber
    public static func _isBridgedToObjectiveC() -> Bool {
        return true
    }
    
    public static func _getObjectiveCType() -> Any.Type {
        return NSNumber.self
    }
    
    public func _bridgeToObjectiveC() -> _ObjectiveCType {
        return NSNumber(integer: self.rawValue)
    }
    
    public static func _forceBridgeFromObjectiveC(source: _ObjectiveCType, inout result: MyEnum?) {
        result = MyEnum(rawValue: source.integerValue)
    }
    
    public static func _conditionallyBridgeFromObjectiveC(source: _ObjectiveCType, inout result: MyEnum?) -> Bool {
        _forceBridgeFromObjectiveC(source, result: &result)
        return true
    }
}

Opinions? Have any/all of these been discussed before?

Thanks!
Mike


(Kevin Lundberg) #2

I was just discussing your first suggestion with a friend the other day. It’d be nice to see that happen so that more apis would be available to obj-c. I wonder how the following would be treated though?

swift:
class Foo: NSObject {
    class func printNumber(num: Int16?) {
        print(num)
    }
}

objc:
[Foo printNumber:[NSNumber numberWithInt:70000]];

Since NSNumber has no restrictions on what it can contain, it’s possible that one could pass a value that is outside the bounds of the type accepted by swift, which would likely crash your code.

If NSNumber were to bridge with optional scalars like this, the wrapped type for NSNumber would need to be present in the type signature of the obj-c code somehow so that the compiler could at the very least warn you. This could perhaps be done via obj-c lightweight generics (or per-scalar-type subclasses of NSNumber for each supported wrapped type, but that’s a bit odd).

You mention [Int] is bridged to NSArray<NSNumber *> * in objc, but [Int16] is not in my tests (probably for the reason I stated above). In my tests, [Bool], [Float], and [Double] also bridge today to that, so the same rules there could apply to optional versions.

···

--
Kevin Lundberg

On Feb 27, 2016, at 5:48 PM, Mike Kasianowicz via swift-evolution <swift-evolution@swift.org> wrote:

Hello, I heard this is the place to talk about things we’d like to see in Swift.

There are a few improvements I’d like to see with Objective-C bridging. I find myself repeating the same three code patterns in order to have the best of both Objective-C and Swift worlds.

First, optional primitive types should map to optional NSNumber. Currently, they only map to NSNumber when used in an array or dictionary. The implicitly generated code would look like the following:

    var optIntProperty: Int?
    
    @objc(optIntProperty)
    var objc_optIntProperty: NSNumber? {
        get {
            guard let value = self.optIntProperty else {
                return nil
            }
            return NSNumber(integer: value)
        }
        set {
            guard let value = newValue else {
                self.optIntProperty = nil
                return
            }
            
            self.optIntProperty = value.integerValue
        }
    }

In the code above, you cannot mark the Int? property dynamic - it causes a compiler error - but it would be nice if you could. This feature would allow shared ObjC classes to be more Swifty, and enable existing runtime reflection/serialization code. I could also see utility of NSNull-nil bridging for values in collections, though this is a rarer case.

Second, it would be nice for C-style structs to bridge to NSValue by default. Currently, every C struct has to implement NSValue bridging manually. I think it’s safe to say that if you mark a struct array property as @objc or dynamic that you typically want it to bridge to NSValue. Additionally, the same optional handling as noted above would be useful.

// pretend this struct were declared in C, or if we get struct exporting in the future (?)
public struct MyCStyleStruct {
    public var intProp: Int
    public var doubleProp: Float
}

extension MyCStyleStruct : _ObjectiveCBridgeable {
    public typealias _ObjectiveCType = NSValue
    public static func _isBridgedToObjectiveC() -> Bool {
        return true
    }
    
    public static func _getObjectiveCType() -> Any.Type {
        return NSValue.self
    }
    
    public func _bridgeToObjectiveC() -> _ObjectiveCType {
        var sself = self
        // would need compiler to create the @encode()
        return NSValue(&sself, withObjCType: "{MyCStyleStruct=if}")
    }
    
    public static func _forceBridgeFromObjectiveC(source: _ObjectiveCType, inout result: MyCStyleStruct?) {
        source.getValue(&result)
    }
    
    public static func _conditionallyBridgeFromObjectiveC(source: _ObjectiveCType, inout result: MyCStyleStruct?) -> Bool {
        _forceBridgeFromObjectiveC(source, result: &result)
        return true
    }
}

Finally, consider automatic bridging of RawRepresentable types (enums) as well, making the _ObjectiveCBridgeable code implicit in the following:

public enum MyEnum: Int {
    case Zero = 0
    case One = 1
    case Two = 2
    case Three = 3
}

extension MyEnum : _ObjectiveCBridgeable {
    public typealias _ObjectiveCType = NSNumber
    public static func _isBridgedToObjectiveC() -> Bool {
        return true
    }
    
    public static func _getObjectiveCType() -> Any.Type {
        return NSNumber.self
    }
    
    public func _bridgeToObjectiveC() -> _ObjectiveCType {
        return NSNumber(integer: self.rawValue)
    }
    
    public static func _forceBridgeFromObjectiveC(source: _ObjectiveCType, inout result: MyEnum?) {
        result = MyEnum(rawValue: source.integerValue)
    }
    
    public static func _conditionallyBridgeFromObjectiveC(source: _ObjectiveCType, inout result: MyEnum?) -> Bool {
        _forceBridgeFromObjectiveC(source, result: &result)
        return true
    }
}

Opinions? Have any/all of these been discussed before?

Thanks!
Mike
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution