[swift-evolution] [Pitch] Making some RawRepresentable things bridge to ObjC as their raw value
Douglas Gregor
dgregor at apple.com
Thu Sep 29 13:45:22 CDT 2016
> On Sep 29, 2016, at 10:16 AM, Joe Groff via swift-evolution <swift-evolution at swift.org> wrote:
>
> 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.
Personally, I consider the first one to be a fairly-low-risk extension to SE-0139 that’s borderline bug-fix. We already know that those types have weak numeric representations in Objective-C because they come from Objective-C, so losing some of the type info by bridging to Objective-C is (IMO) falls out of having strong types in Swift for weaker types in Objective-C.
The second one makes me a little nervous, I think because it weakens typing for types defined in Swift. These types don’t naturally have Objective-C counterparts, so if we’re going to weaken the types, it feels like we should only do so via some explicit conformance (e.g., to a publicly-available form of _ObjectiveCBridgeable).
- Doug
More information about the swift-evolution
mailing list