- Proposal: SE-0139
- Author: Joe Groff
- Review Manager: Doug Gregor
- Status: Implemented (Swift 3.0.1)
- Decision Notes: Rationale
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
NSRange
by boxing them into NSValue
objects.
Swift-evolution thread: here
SE-0116
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! JSONSerialization.data(withJSONObject: plist)
let j: UInt8 = 38
let brokenPlist = ["thirty-eight": j]
// Will throw because `j` didn't bridge to a JSON type
try! JSONSerialization.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
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 Animation as an example in
this bug report. 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
.
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
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. Note that, although NSValue
has factory initializers and
accessors for each of the above struct types, the bridging implementation
ought to stick to NSValue
's core valueWithBytes:objCType:
and getValue:
API, to avoid potential availability issues with the type-specific methods.
NSNumber
is a bit trickier, since Cocoa's implementation does not generally
guarantee to remember the exact number type an instance was constructed from.
When we bridge Swift number types to NSNumber
, though, we use specific
NSNumber
subclasses to preserve the original Swift type, and in these cases
we can check the exact Swift type in dynamic casts. For NSNumbers from
Cocoa, we can 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 NSNumber
can
potentially be cast to a different type from the original value.
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 Any
to id
to Any
and back by dynamic casting.
The ability to reliably distinguish the exact number type that an NSNumber
was constructed from would be lost.
We can of course do nothing and leave the behavior as-is.
NSValue
also carries factories for valueWithPointer:
and
valueWithNonretainedObject:
. Maybe we could bridge
UnsafePointer
and Unmanaged
this way, but we probably shouldn't.
Instead of implementing NSValue
bridging in the overlay, Zach Waldowski
suggests using Objective-C's __attribute__((objc_boxable))
, which enables
autoboxing of a struct in ObjC with @(...)
syntax, to also instruct Swift's
Clang importer to synthesize a bridge to NSValue
automatically for types
annotated with the attribute. However, this attribute hasn't been widely
adopted in Apple SDKs.