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

Brent Royal-Gordon brent at architechies.com
Mon Apr 4 23:22:50 CDT 2016


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

There are a number of things I'm not really clear on.

* * *

Consider this Objective-C API:

	ObjCFizzer* myFizzer;

Which of these represents how it is imported?

	var myFizzer: ObjCFizzer
	var myFizzer: Fizzer

Suppose there is also a subclass (say, ObjCMutableFizzer), and we have this Objective-C API:

	ObjCMutableFizzer* mutableFizzer;

Which of these represents how it is imported?

	var myMutableFizzer: ObjCMutableFizzer
	var myMutableFizzer: Fizzer

On the basis of NSArray and friends, I assume they come across like this:

	var myFizzer: Fizzer
	var myMutableFizzer: ObjCMutableFizzer

Is that correct?

* * *

I assume that you can use casts to implicitly cross the bridge in both directions, even when Objective-C is not involved. That is, you could write something like this:

	ObjCFizzer() as Fizzer

Is that correct?

If you can cross the bridge purely in Swift, does the object type actually have to be @objc? Why?

If it does not have to be @objc, is this perhaps better thought of as an `ObjectBridgeable` protocol which allows cast-based conversion of any type to an equivalent class, and which also does Objective-C bridging if the class happens to be @objc? (This looser definition might help us if we ever interoperate with other object systems on different platforms.)

* * *

Suppose you have a value of type `ObjCMutableFizzer`:

	let mutableFizzer: ObjCMutableFizzer = ...

Can you write `mutableFizzer as! Fizzer`? In other words, if a type is bridged to a particular class, is it also bridged to its subclasses?

Based on the examples from Foundation, I suspect the answer is "yes".

* * *

Foundation classes can sometimes be bridged using an upcast (a plain `as`), which cannot crash. Is this possible with ObjectiveCBridgeable? If so, how? If not, will Foundation classes lose that ability?

If this feature can't be expressed with ObjectiveCBridgeable, is this seen as a shortcoming we should try to overcome, or the proper design? I worry about the unnecessary proliferation of exclamation points, especially since many style guides strongly discourage them, which will turn this into an unnecessary proliferation of unnecessary `if let`s.

* * *

I'm confused by a statement in the "Ambiguity and Casting" section:

	2. 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.
		i. The compiler should emit a diagnostic when it detects two Swift types attempting to bridge to the same ObjectiveCType.

Does this mean that each bridged class must have exactly one corresponding Swift type? Or does it mean that if a bridged type has more than one corresponding Swift class, an explicit cast is always needed? Or merely that it may sometimes be needed?

There are examples in the frameworks of many types bridging to a single class. For instance, a whole hoard of numeric types bridge to NSNumber; Swift handles this by exposing Swift types like `[Int]` as `NSArray<NSNumber*>*`, but doesn't do any converting when going in the opposite direction. Is that how this new protocol works, or does it do something else? 

* * *

I'm confused by the SWIFT_BRIDGED() macro. Why does it exist? Doesn't the ObjectiveCBridgeable conformance provide all the information needed? What happens if you don't include it? (Perhaps you get the one-way bridging behavior seen with `NSNumber`?)

* * *

The "Resilience" section says:

	Adding or removing conformance to ObjectiveCBridgeable, or changing the ObjectiveCType is a fragile (breaking) change.

Why is this? In particular, why is adding a conformance a breaking change? That isn't the normal rule for protocols.

* * *

Probably a stupid question, but I want to be certain since the example does something else: There would not be some kind of circularity problem with defining `ObjCFizzer` in terms of `Fizzer`, would there? For instance:

	class ObjCFizzer: NSObject {
		fileprivate var fizzer: Fizzer

		fileprivate init(_ fizzer: Fizzer) {
			self.fizzer = fizzer
		}
				
		var fizzyString: String? {
			guard case .case1(let string) = fizzer else { return nil }

			return string
		}
		
		var fizzyX: Int? {
			guard case .case2(let x, _) = fizzer else { return nil }
			return x
		}
		
		var fizzyY: Int? {
			guard case .case2(_, let y) = fizzer else { return nil }
			return y
		}
	}
	extension ObjCFizzer {
		convenience init(string: String) {
			self.init(.case1(string))
		}
		
		convenience init(x: Int, y: Int) {
			fizzer = .case2(x, y)
		}
	}

I have a place in my code where I would like to use ObjectiveCBridgeable, but it uses a pattern like this, so I'd like to make sure I'll be able to adopt the feature.

* * *

In general, I feel like this proposal is written in a way which is accessible to the people who participated in the original discussion, but not to others.

This is a proposal which contains seven uses of the word "thunk" and zero uses (as opposed to conformances) of the proposed protocol in examples. It seems to imply that existing bridging of Foundation types might change, but does not explain what those changes will mean for user code. It is fundamentally an Objective-C bridging feature, but it contains no examples of Objective-C.

In theory I think this proposal is a great idea, but in practice I understand the specifics of it so poorly that I can't really endorse it without some clarifications. Hopefully someone can explain things a little more clearly so that I can lend my support, or at least give a more specific and helpful critique.

-- 
Brent Royal-Gordon
Architechies



More information about the swift-evolution mailing list