[swift-evolution] [Idea] ObjectiveCBridgeable

Russ Bishop xenadu at gmail.com
Wed Mar 23 02:21:02 CDT 2016

> On Mar 21, 2016, at 9:41 AM, Douglas Gregor <dgregor at apple.com> wrote:
>> On Mar 9, 2016, at 12:26 PM, Russ Bishop via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>> An official proposal PR has been opened on this: https://github.com/apple/swift-evolution/pull/198 <https://github.com/apple/swift-evolution/pull/198>
>> It includes clarifications, a few changes, and some better examples.
> FWIW, I’ve implemented much of this proposal as a series of compiler cleanups. It introduces a few tweaks to the _ObjectiveCBridgeable protocol that were necessary to generalize (and de-special-case) the NSString/NSArray/NSDictionary/NSSet bridging, but it’s still considered an implementation detail. So, some comments on the proposal itself…
> /// A type adopting `ObjectiveCBridgeable` will be exposed
> /// to Objective-C as the type `ObjectiveCType`
> public protocol ObjectiveCBridgeable: _ObjectiveCBridgeable {
>     associatedtype ObjectiveCType : AnyObject
>     associatedtype _ObjectiveCType = ObjectiveCType
> I think we should just say that ObjectiveCBridgeable replaces _ObjectiveCBridgeable, and only have the first associated type. (You actually wanted a typealias anyway, I think).

That sounds great to me. 

>     /// Returns `true` iff instances of `Self` can be converted to
>     /// Objective-C.  Even if this method returns `true`, a given
>     /// instance of `Self._ObjectiveCType` may, or may not, convert
>     /// successfully to `Self`.
>     ///
>     /// A default implementation returns `true`.
>     @warn_unused_result
>     static func isBridgedToObjectiveC() -> Bool
> It’s probably worth saying why someone might override this method: usually, it’s because the Swift type is generic and it only makes sense to bridge for some type arguments. Granted, there is no way to communicate this information to the compiler, which is a bit of a hole in the design. For example, Array<T> only bridges to NSArray when T is itself representable in Objective-C. We really need conditional conformances to for this part of the feature to work properly.

I’ve added a section to the proposal to call out that it is intended for conformance to be conditional in some cases (eg Array), and a small explanation of what to do and a code example. It’s a bit unwieldy but workable until we have conditional conformance.

>     /// Try to construct a value of the Self type from
>     /// an Objective-C object of the bridged class type.
>     ///
>     /// If the conversion fails this initializer returns `nil`.
>     init?(bridgedFromObjectiveC: ObjectiveCType)
> FWIW, implementing this required a new “unconditional” entry point used by the compiler:
>   static func _unconditionallyBridgeFromObjectiveC(source: _ObjectiveCType?)
>       -> Self
> It can get a default implementation, and should be a non-failable initializer.

I’ve updated the proposal to add this. The default implementation of "init(unconditionallyBridgedFromObjectiveC: ObjectiveCType?)" just calls the fallible initializer and aborts if anything goes wrong. Default implementation of “unconditionallyBridgeFromObjectiveC(source: ObjectiveCType?)” calls the initializer. My guess is most types will probably just accept the default behavior.

I assume this is a static function to avoid allocating memory by calling the initializer directly for each element, given the point is to defer the work? I wonder if we can skip the static though and just call the initializer directly? It would simplify the protocol a tiny bit.

> 	4. The ObjectiveCType must be defined in Swift. If a -swift.h header is generated, it will include a SWIFT_BRIDGED()macro where the parameter indicates the Swift type with which the ObjectiveCType bridges. The macro will be applied to the ObjectiveCType and any subclasses.
> This is unnecessarily restrictive, and eliminates the “make my Objective-C class bridge into a Swift value type” case that (for example) the compiler already does for String/Array/Dictionary/Set. I think there are two cases:
> 	(a) The ObjectiveCType is defined in Objective-C. It must be an Objective-C class with the attribute swift_bridge(“Bar”), where “Bar” is the name of the bridged Swift type.
> 	(b) The ObjectiveCType is defined in Swift, in which case it must be an @objc class. When emitting the generated header, the appropriate swift_bridge attribute will be added to the @interface declaration.

I mostly hadn’t really considered the implications but I see the value and I trust your assessment; I removed the restriction and reworded the whole section.

> (This was #5) It is an error for bridging to be ambiguous.
> A Swift type may bridge to an Objective-C base class, then provide different subclass instances at runtime but no other Swift type may bridge to that base class or any of its subclasses.
> The compiler must emit a diagnostic when it detects two Swift types attempting to bridge to the same ObjectiveCType.
> This is a tricky area. Currently, Int/Float/Double/Bool/CGFloat/UInt all have _ObjectiveCBridgeable conformances, although those conformances only really kick in at runtime (e.g., when dynamically casting an [AnyObject] or [NSNumber] to [Int] or [Double] with as? or matching a switch case). They would run afoul of this rule. However, this rule does generally make sense: if two Swift types have the same ObjectiveCType, we won’t know how to map an Objective-C API back into Swift. Those numeric types only work because they are trivially mapped between Swift and (Objective-)C; they don’t need to go through the _ObjectiveCBridgeable conformance. 

Perhaps the rule should simply be that any Objective-C API imported that has ambiguity is imported as the Objective-C type without automatic bridging support. The compiler can continue to import Int and friends with special magic. Creating an ambiguity just leaves you to resolve the problem manually? The rule would be something like "omitting the SWIFT_BRIDGED() attribute from ObjC //or// multiple Swift types bridging to the same ObjC type" turns off automatic thunk generation but bridged collections will still call the protocol where appropriate.

> On the other hand, the greater ambiguity problem is if there are two conformances to ObjectiveCBridgeable on the same type; that’s already covered by Swift’s rules about multiple conformances.
> (This was #6) The Swift type and ObjectiveCType must be defined in the same module
> Yes, absolutely. If the ObjectiveCType comes from Objective-C, then it must come from the same-named Objective-C module.
> Under “Alternatives considered”, there is a typo “fesible”.
> I’ve been assuming we’re only allowed to bridge with Objective-C classes (whether they are defined in Swift or Objective-C), but Swift also has @objc enums now… I assume you don’t intend @objc enums to be part of this, so I think it makes sense to be explicit about the limitation to classes.

Yes that’s true. I added wording to that effect to the proposal.

An update has been posted to https://github.com/russbishop/swift-evolution/blob/master/proposals/0000-objectivecbridgeable.md <https://github.com/russbishop/swift-evolution/blob/master/proposals/0000-objectivecbridgeable.md>

Would you prefer if I did / did not add your name to the proposal? I feel guilty taking all the credit.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160323/4161aeb3/attachment.html>

More information about the swift-evolution mailing list