Introduction

A handful of Swift numeric types are bridged to NSNumber when passed into 
Objective-C object contexts. We should extend this bridging behavior to all 
Swift numeric types. We should also bridge common Cocoa structs such as 
NSRangeby boxing them into NSValue objects.

Swift-evolution thread: TBD <https://lists.swift.org/pipermail/swift-evolution/>
 
<https://github.com/jckarter/swift-evolution/blob/be49e08f56450ffea394306198bcd25f58915e30/proposals/XXXX-bridge-nsnumber-and-nsvalue.md#motivation>Motivation

SE-0116 
<https://github.com/apple/swift-evolution/blob/master/proposals/0116-id-as-any.md>
 changed how Objective-C's id and untyped collections import into Swift to use 
the Any type. This makes it much more natural to pass in Swift value types such 
as String and Array, but introduces the hazard of passing in types that don't 
bridge well to Objective-C objects. Particularly problematic are number types; 
whereas Int, UInt, and Double will automatically bridge as NSNumber, 
other-sized numeric types fall back to opaque boxing:

let i = 17
let plist = ["seventeen": i]
// OK
try! NSJSONSerialization.data(withJSONObject: plist)

let j: UInt8 = 38
let brokenPlist = ["thirty-eight": j]
// Will throw because `j` didn't bridge to a JSON type
try! NSJSONSerialization.data(withJSONObject: brokenPlist)
We had shied away from enabling this bridging for all numeric types in the 
Swift 1.x days, among other reasons because we allowed implicit bridging 
conversions in both directions from Swift value types to NS objects and back, 
which meant that you could slowly and brokenly convert between any two numeric 
types transitively via NSNumber if we allowed this. We killed the implicit 
conversions completely with SE-0072 
<https://github.com/apple/swift-evolution/blob/master/proposals/0072-eliminate-implicit-bridging-conversions.md>
 so that is no longer a concern, so expanding the bridging behavior should no 
longer be a major problem, since it must now always be explicitly asked for.

There are also many Cocoa APIs that accept NSArray and NSDictionary objects 
with members that are NSValue-boxed structs. Matt Neuberg highlights Core 
Automation as an example in this bug report 
<https://bugs.swift.org/browse/SR-2414>. With id-as-Any, it's natural to expect 
this to work:

anim.toValue = CGPoint.zero
However, the CGPoint value does not box as a meaningful Objective-C object, so 
this currently breaks Core Animation at runtime despite compiling successfully. 
It would be more idiomatic to bridge these types to NSValue.

 
<https://github.com/jckarter/swift-evolution/blob/be49e08f56450ffea394306198bcd25f58915e30/proposals/XXXX-bridge-nsnumber-and-nsvalue.md#proposed-solution>Proposed
 solution

All of Swift's number types should be made to bridge to NSNumber when used as 
objects in Objective-C:

Int8
Int16
Int32
Int64
UInt8
UInt16
UInt32
UInt64
Float
Double
Cocoa structs with existing NSValue factory and property support should be made 
to bridge to NSValue when used as objects:

NSRange
CGPoint
CGVector
CGSize
CGRect
CGAffineTransform
UIEdgeInsets
UIOffset
CATransform3D
CMTime
CMTimeRange
CMTimeMapping
MKCoordinate
MKCoordinateSpan
SCNVector3
SCNVector4
SCNMatrix4
 
<https://github.com/jckarter/swift-evolution/blob/be49e08f56450ffea394306198bcd25f58915e30/proposals/XXXX-bridge-nsnumber-and-nsvalue.md#detailed-design>Detailed
 design

Bridged NSNumber and NSValue objects must be castable back to their original 
Swift value types. NSValue normally preserves the type information of its 
included struct in its objCType property. We can check the objCType of an 
NSValue instance when attempting to cast back to a specific bridged struct type.

NSNumber is a bit trickier, since Cocoa's implementation does not generally 
guarantee to remember the exact number type an instance was constructed from. 
We can instead say that casting an NSNumber to a Swift number type succeeds if 
the value of the NSNumber is exactly representable as the target type. This is 
imperfect, since it means that an NSNumbercan potentially be cast to a 
different type from the original value, but it at least ensures that Swift 
values round-trip through the bridge without depending on NSNumber 
implementation details.

 
<https://github.com/jckarter/swift-evolution/blob/be49e08f56450ffea394306198bcd25f58915e30/proposals/XXXX-bridge-nsnumber-and-nsvalue.md#impact-on-existing-code>Impact
 on existing code

This change has no static source impact, but changes the dynamic behavior of 
the Objective-C bridge. From Objective-C's perspective, values that used to 
bridge as opaque objects will now come in as semantically meaningful 
Objective-C objects. This should be a safe change, since existing code should 
not be relying on the behavior of opaque bridged objects. From Swift's 
perspective, values should still be able to round-trip from concrete number and 
struct types to Anyto id to Any and back by dynamic casting. The ability to 
reliably distinguish the exact number type that an NSNumberwas constructed from 
would be lost.

 
<https://github.com/jckarter/swift-evolution/blob/be49e08f56450ffea394306198bcd25f58915e30/proposals/XXXX-bridge-nsnumber-and-nsvalue.md#alternatives-considered>Alternatives
 considered

We can of course do nothing and leave the behavior as-is.

NSValue also carries factories for valueWithPointer: and 
valueWithNonretainedObject:. Maybe we could bridgeUnsafePointer and Unmanaged 
this way, but we probably shouldn’t.

        - Doug
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to