In the discussion around SE-0139 (bridging NSNumber and NSValue), many people
pointed out that it's common in ObjC to use NSNumbers holding enum constants in
Cocoa containers. Many ObjC interfaces look something like this:
/// A washing machine.
@interface QXWashingMachine
/// Wash cycles supported by the washing machine.
typedef NS_ENUM(NSInteger, QXWashCycle) {
QXWashNormal,
QXWashDelicates,
QXWashLinens,
};
/// Perform a sequence of wash cycles. Takes an NSArray of QXWashCycle
constants passed
/// as NSNumbers.
- (void)performWashCycles:(NSArray *)cycles;
@end
In ObjC, you can call this API like this:
[washingMachine performWashCycles:@[
@(QXWashLinens),
@(QXWashDelicates),
]];
In Swift 3, the equivalent code will compile, but fail at runtime, because the
enum type falls back to opaque object bridging instead of using NSNumbers:
// Fails at runtime, because WashCycle constants don't implicitly
bridge to NSNumber
washingMachine.perform(washCycles: [WashCycle.linens,
WashCycle.delicates])
so you have to know to get the `rawValue`s out first:
// Fails at runtime, because WashCycle constants don't implicitly
bridge to NSNumber
washingMachine.perform(washCycles: [WashCycle.linens.rawValue,
WashCycle.delicates.rawValue])
We encountered similar problems last year as we developed the `swift_newtype`
ObjC feature last year, which enabled us to import some stringly-typed ObjC
APIs with stronger types in Swift. A type like `Notification.Name`, which
represents a set of NSString constants, is imported to be RawRepresentable as
String and also to bridge to Objective-C as one, by having the compiler
implicitly generate a conformance to the internal _ObjectiveCBridgeable
protocol for it. We could conceivably do one of the following things:
- Have the compiler generate a bridging conformance for imported NS_ENUM and
NS_OPTIONS types, and perhaps also for @objc enums defined in Swift, like we do
for newtypes.
- Alternatively, we could say that *anything* with a RawRepresentable
conformance bridges to ObjC as its rawValue.
The second one is conceptually appealing to me, since RawRepresentable is
already something newtypes, enums, and option sets have in common in Swift, but
I think it may be too lax—it would effectively make RawRepresentable the user
interface to Objective-C bridging, which I'm not sure is something we want.
Limiting the bridging behavior to @objc enums and imported option sets limits
the impact, though there are still tradeoffs to consider:
- Adding any new bridging behavior has the potential to break existing code
that relies on the current opaque object bridging. We tell people not to do
that, of course, but that's no guarantee that people don't.
- As with newtypes, the bridged Objective-C representation loses type
information from Swift, meaning that dynamic casts potentially need to become
"slushier". Swift maintains the distinction between Notification.Name and
String, for example, but once a value of either type has been bridged to
NSString, the distinction is lost, so we have to allow casts from NSString back
to either String or Notification.Name. If we bridge enums and option sets, we
would similarly lose type information once we go to NSNumber. We can in some
cases mitigate this for class clusters like NSString or NSNumber by using our
own subclasses that preserve type info, which can at least preserve type info
for Swift-bridged objects, though we can't do this universally for all
Cocoa-sourced objects.
-Joe
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution