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

Reply via email to