[swift-evolution] [Pitch] Making some RawRepresentable things bridge to ObjC as their raw value

Joe Groff jgroff at apple.com
Thu Sep 29 12:16:37 CDT 2016


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


More information about the swift-evolution mailing list