[swift-evolution] [Review] SE-0058: Allow Swift types to provide custom Objective-C representations

Brent Royal-Gordon brent at architechies.com
Tue Apr 5 20:06:31 CDT 2016


> https://github.com/apple/swift-evolution/blob/master/proposals/0058-objectivecbridgeable.md

>    But then, these *are* intended to be full-width type conversions,
>    are they not?  Why not these:
> 
>      init?(_ source: ObjectiveCType)
>      init(_ source: ObjectiveCType?)
> 
>    ?  If any of these transformations are wrong, it would good to have
>    a rationale as to why.

I really don't like these two differing only by the optionality of their argument and return type, especially if they're going to have *no* argument label, which will make them look very attractive. And the unconditional one has many strange semantics—the extra layer of optionality on the argument, the fact that it may *lazily* crash if you access a sub-object which isn't permitted, etc. So I would at least label that one:

	init?(_ source: ObjectiveCType)
	init(forced source: ObjectiveCType?)

I'm also a little worried about having an `init?(_:)` when a particular conversion might reasonably always succeed. The lack of a label will imply to users that this is something they can use directly; 

> 3. Is this proposal introducing a backdoor people can exploit to create
>   user-defined implicit conversions?  If so, shouldn't that worry us?

This proposal reads to me like it permits conversions by casting, but not by simply using one type where another belongs.

> 4. This proposal should probably support creating a type that only
>   bridges *from* Objective-C, to handle bridging mutable Objective-C
>   classes without an immutable base class to value types (anything else
>   breaks one of the languages' expectations of value or reference
>   semantics).  This suggests a hierarchy of two protocols might be
>   warranted.

I agree.

Here's an alternative design. It does a few things:

* Splits bridging from and bridging to Objective-C so they can be applied independently.
* Splits conditional and unconditional bridging from Objective-C. 
* Explicitly marks Objective-C types with their Swift equivalents, even in Swift, to make it clear that each Objective-C type bridges to exactly one Swift type, and which type that is. (This allows you to have multiple conversions between various Swift types and a single Objective-C type, while explicitly specifying which one, if any, should be used when translating Objective-C APIs to Swift.)

I'm using the proposed SE-0041 protocol naming conventions (https://github.com/apple/swift-evolution/blob/master/proposals/0041-conversion-protocol-conventions.md) to name the protocols. I'm also assuming the presence of conditional conformances and where clauses on associated types; these protocols would have to be temporarily hobbled to work within the type system's current capabilities.

	/// Conforming types can be cast from Self to ObjectiveCType using `as` (or a subtype using `as?` 
	/// or `as!`), and Swift APIs taking or returning Self are exposed to Objective-C as ObjectiveCType.
	protocol ObjectiveCRepresentable {
		associatedtype ObjectiveCType: AnyObject
		func bridged() -> ObjectiveCType
	}
	
	/// Conforming types can be cast from ObjectiveCType or a subtype to Self using `as?` or `as!`.
	/// 
	/// -SeeAlso: ObjectiveCUnconditionallyCreatable, ObjectiveCBridgeable
	protocol ObjectiveCCreatable {
		associatedtype ObjectiveCType: AnyObject
		init?(_ source: ObjectiveCType)
		init(forced source: ObjectiveCType?)
	}
	
	/// Conforming types can be cast from UnconditionalObjectiveCType to Self using `as`.
	/// 
	/// -Note:	A type can conform to both this protocol and ObjectiveCCreatable to 
	///		allow both an unconditional exact cast and a conditional inexact one. 
	///		For instance, Array can have ObjectiveCType = NSArray<AnyObject> 
	///      	and UnconditionalObjectiveCType = NSArray<T>.
	protocol ObjectiveCUnconditionallyCreatable {
		associatedtype UnconditionalObjectiveCType: AnyObject
		init(_ source: UnconditionalObjectiveCType?)
	}
	
	/// Conforming types are translated from Self to SwiftType in APIs imported from Objective-C.
	/// 
	/// -Note:	Objective-C headers can apply this protocol to their classes using the
	///		SWIFT_BRIDGED("SwiftType") attribute.
	/// -Remark: This could instead be indicated with a Swift-side attribute.
	protocol ObjectiveCBridgeable: class {
		associatedtype SwiftType: ObjectiveCUnconditionallyCreatable where SwiftType.UnconditionalObjectiveCType == Self
	}

Interestingly, another alternative would be to remove the bridging semantic from ObjectiveCRepresentable and put it in a subprotocol:

	/// Conforming types can be cast from Self to ObjectiveCType using `as` (or a subtype using `as?` 
	/// or `as!`).
	protocol ObjectiveCRepresentable { … }
	
	/// Conforming types will be translated from Self to ObjectiveCType in APIs exported to Objective-C
	protocol SwiftToObjectiveCBridgeable: ObjectiveCRepresentable {}
	
	/// Conforming types are translated from Self to SwiftType in APIs imported from Objective-C.
	/// 
	/// -Note:	Objective-C headers can apply this protocol to their classes using the
	///		SWIFT_BRIDGED("SwiftType") attribute.
	protocol ObjectiveCToSwiftBridgeable: class {
		associatedtype SwiftType: ObjectiveCUnconditionallyCreatable where SwiftType.UnconditionalObjectiveCType == Self
	}

Then the Representable protocol and the two Creatable protocols are no longer Objective-C-specific—they simply indicate that a type can be cast to an equivalent object type. We can then make them into ObjectRepresentable, ObjectCreatable, and ObjectUnconditionalCreatable. (This is good because we will presumably want to continue, for instance, casting NSNumber to Int in Corelibs Foundation code.)

-- 
Brent Royal-Gordon
Architechies



More information about the swift-evolution mailing list