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


(Joe Groff) #1

Hello Swift community,

The review of “Allow Swift types to provide custom Objective-C representations” begins now and runs through April 11, 2016. The proposal is available here:

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

Reviews are an important part of the Swift evolution process. All reviews should be sent to the swift-evolution mailing list at

  https://lists.swift.org/mailman/listinfo/swift-evolution

or, if you would like to keep your feedback private, directly to the review manager. When replying, please try to keep the proposal link at the top of the message:

  Proposal link:

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

  Reply text

    Other replies

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  • What is your evaluation of the proposal?
  • Is the problem being addressed significant enough to warrant a change to Swift?
  • Does this proposal fit well with the feel and direction of Swift?
  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

More information about the Swift evolution process is available at

https://github.com/apple/swift-evolution/blob/master/process.md

Thank you,

-Joe
Review Manager


(Brent Royal-Gordon) #2

  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


(Howard Lovatt) #3

• What is your evaluation of the proposal?

Good idea, it gets rid of compiler magic

• Is the problem being addressed significant enough to warrant a change to
Swift?

Yes, plenty of mixed projects. Plenty of Cocoa libraries that could be
improved with a Swift API.

• Does this proposal fit well with the feel and direction of Swift?

Yes, obj-c is still important

• If you have used other languages or libraries with a similar feature, how
do you feel that this proposal compares to those?

No, but have wanted this in Swift

• How much effort did you put into your review? A glance, a quick reading,
or an in-depth study?

Followed discussions on swift-evolution

···

On Tuesday, 5 April 2016, Joe Groff via swift-evolution < swift-evolution@swift.org> wrote:

Hello Swift community,

The review of “Allow Swift types to provide custom Objective-C
representations” begins now and runs through April 11, 2016. The proposal
is available here:

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

Reviews are an important part of the Swift evolution process. All reviews
should be sent to the swift-evolution mailing list at

https://lists.swift.org/mailman/listinfo/swift-evolution

or, if you would like to keep your feedback private, directly to the
review manager. When replying, please try to keep the proposal link at
the top of the message:

Proposal link:

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

Reply text

Other replies

*What goes into a review?*

The goal of the review process is to improve the proposal under review
through constructive criticism and, eventually, determine the direction of
Swift. When writing your review, here are some questions you might want to
answer in your review:

• What is your evaluation of the proposal?
• Is the problem being addressed significant enough to warrant a change to
Swift?
• Does this proposal fit well with the feel and direction of Swift?
• If you have used other languages or libraries with a similar feature,
how do you feel that this proposal compares to those?
• How much effort did you put into your review? A glance, a quick reading,
or an in-depth study?

More information about the Swift evolution process is available at

https://github.com/apple/swift-evolution/blob/master/process.md

Thank you,

-Joe
Review Manager

--
-- Howard.


(Dave Abrahams) #4

A few thoughts:

1. It would have made it easier to evaluate if the proposal had not
   talked about removing _ObjectiveCBridgeable and replacing it with a
   new ObjectiveCBridgeable protocol, but what could more accurately be
   described as de-underscoring the existing protocol and slightly
   modernizing its API. The existing protocol was always intended to be
   suitable for general use this way, and the design remains
   substantially unchanged, so I support the proposal in principle.

2. These two APIs should look different IMO:

      init?(bridgedFromObjectiveC: ObjectiveCType)
      init(unconditionallyBridgedFromObjectiveC: ObjectiveCType?)

    they should definitely use “bridging” instead of “bridged,” since the
    phrase describes *how* the conversion is going to be done.

      init?(bridgingFromObjectiveC: ObjectiveCType)
      init(unconditionallyBridgingFromObjectiveC: ObjectiveCType?)

    Then, the “ObjectiveC” in the names looks like redundant type
    information, suggesting this would be better:

      init?(bridgingFrom: ObjectiveCType)
      init(unconditionallyBridgingFrom: ObjectiveCType?)

    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.

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

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.

5. For the version of the protocol that does bridge back to Objective-C,
   maybe something should be done to make it more clear that the
   ObjectiveCType must be immutable. Perhaps that means doing this
   without a protocol hierarchy and using a different associated type
   name in the two protocols; I don't know.

···

on Mon Apr 04 2016, Joe Groff <swift-evolution@swift.org> wrote:

Hello Swift community,

The review of “Allow Swift types to provide custom Objective-C representations”
begins now and runs through April 11, 2016. The proposal is available here:

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

Reviews are an important part of the Swift evolution process. All reviews should
be sent to the swift-evolution mailing list at

https://lists.swift.org/mailman/listinfo/swift-evolution

or, if you would like to keep your feedback private, directly to the review
manager. When replying, please try to keep the proposal link at the top of the
message:

Proposal link:

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

Reply text

Other replies

What goes into a review?

The goal of the review process is to improve the proposal under review through
constructive criticism and, eventually, determine the direction of Swift. When
writing your review, here are some questions you might want to answer in your
review:

• What is your evaluation of the proposal?
• Is the problem being addressed significant enough to warrant a change to
Swift?
• Does this proposal fit well with the feel and direction of Swift?
• If you have used other languages or libraries with a similar feature, how do
you feel that this proposal compares to those?
• How much effort did you put into your review? A glance, a quick reading, or an
in-depth study?

--
Dave


(Kevin Lundberg) #5

Generally I'm +1 on this, but I do have a concern. It's not made
explicit in the proposal, but I presume that this is meant to only be
available on Darwin, and not Linux or other platforms that don't have
swift using the Objective-C runtime? (Please correct me if I am mistaken.)

I ask because of the swift-corelibs-foundation project; Presumably once
that is complete for Swift 3, code that makes use of this type bridging
should be able to be run cross-platform. For example:

var a = [AnyObject]()
(a as NSArray).addObject(NSObject()) // should this work on all platforms?

swift-corelibs-foundation uses its own protocol named
_ObjectTypeBridgeable (defined here:
https://github.com/apple/swift-corelibs-foundation/blob/338f4bf3a89c75a0420b49f5701466e106af02b5/Foundation/NSSwiftRuntime.swift#L205)
to simulate what happens today on Darwin platforms, but there is no
language support for it so API consumers must explicitly call its
bridging methods. It would be great if corelibs-foundation (and any code
designed to work on Linux) could take advantage of this proposal to
provide the language support, and if the proposal as written is not
intended to have an effect on Linux at all, then I would suggest we
modify it so that the resulting implementation is not tied to
Objective-C and Darwin only.

-Kevin

···

On 4/4/2016 1:44 PM, Joe Groff via swift-evolution wrote:

Hello Swift community,

The review of “Allow Swift types to provide custom Objective-C
representations” begins now and runs through April 11, 2016. The
proposal is available here:

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

Reviews are an important part of the Swift evolution process. All
reviews should be sent to the swift-evolution mailing list at

https://lists.swift.org/mailman/listinfo/swift-evolution

or, if you would like to keep your feedback private, directly to the
review manager. When replying, please try to keep the proposal link at
the top of the message:

Proposal link:

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

Reply text

Other replies

*What goes into a review?*

The goal of the review process is to improve the proposal under review
through constructive criticism and, eventually, determine the
direction of Swift. When writing your review, here are some questions
you might want to answer in your review:

• What is your evaluation of the proposal?
• Is the problem being addressed significant enough to warrant a
change to Swift?
• Does this proposal fit well with the feel and direction of Swift?
• If you have used other languages or libraries with a similar
feature, how do you feel that this proposal compares to those?
• How much effort did you put into your review? A glance, a quick
reading, or an in-depth study?

More information about the Swift evolution process is available at

https://github.com/apple/swift-evolution/blob/master/process.md

Thank you,

-Joe
Review Manager

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Russ Bishop) #6

  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

The latter. The idea is that the importer sees the bridged type is available and substitutes it on all imported signatures. The actual mechanics of that will involve some generated code (thunk) to call the protocol. I could update the proposal to include what the body of that thunk might look like but it didn’t seem terribly interesting.

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

The intention there is that it imports as the bridged type so the latter.

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

  var myFizzer: Fizzer
  var myMutableFizzer: ObjCMutableFizzer

Is that correct?

No

* * *

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.)

In theory you could do this, though it would be a useless waste of CPU cycles :slight_smile:

My thinking was that we may need similar bridging protocols in some hypothetical world where we can import C++ objects but the shape of the protocol (and certainly the constraints on the associated type) would be quite different.

* * *

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”.

Yes, you can invoke as? or as! casts which will call the respective initializers, though the default implementation of unconditional bridging (as!) will call the conditional initializer.

* * *

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.

This would not be possible. This sort of bridging only works with special magic types because they are known to always succeed. There is no condition under which Swift will fail to convert String to NSString. The compiler/runtime can’t prove that about any arbitrary type.

For bridging an Objective-C library into Swift, ideally all the APIs will be annotated with SWIFT_BRIDGED so on import the Swift code won’t even be aware the Objective-C type exists. All you’ll see in Swift is the appropriate Swift types. This gives a library (say Photos.framework or UIKit) the chance to provide truly native Swift types by shipping a module with combined Swift and Objective-C code.

Similarly, going the other direction (an app with Objective-C and Swift code) this proposal eliminates the need to deal with the Objective-C types in Swift.

The only situation where casting might be required is interop with things like performSelector, context objects, or when SWIFT_BRIDGED annotations are missing.

* * *

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?

As of right now, the proposal says that is an error to define in Swift code and the compiler will generate an error.

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’ve gone back and forth on the ambiguity section (In one version of the proposal, ambiguity just disabled automatic thunk generation and required you to perform manual casting). NSNumber brings up an interesting case. Several Swift types should adopt NSNumber as their bridging target, but wouldn’t be able to under the proposed rule. However allowing this for the general case may be problematic. There is already special compiler magic for importing things like NSInteger and Int bridges to NSInteger or NSNumber depending on whether it is inside a collection. I’m curious what other people’s thoughts are on this.

* * *

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`?)

For an Objective-C framework, the definitions of the @objc types will be in Objective-C headers, not an auto-generated Swift bridging header. The macro is both for the auto-generated header but also for library writers to use in their Objective-C headers.

* * *

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.

Because it changes the shape of the API of a client importing it.

For similar reasons requiring the bridging to be in same-named modules is to prevent the shape of an imported API changing because you imported a separate module that added some bridging conformance.

* * *

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.

You can certainly “wrap” the underlying Swift type and expose accessors for it as you’ve done here. There are no issues with circularity at all.

I realize that the proposal doesn’t explicitly state it but generally a type that is @objc can’t conform to ObjectiveCBridgeable. (Even if it did, the conformance would be useless.)

* * *

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.

The intent is for existing foundation types to continue bridging just as they do today with no changes.

Russ

···

On Apr 4, 2016, at 9:22 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:


(Brent Royal-Gordon) #7

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


(Russ Bishop) #8

A few thoughts:

1. It would have made it easier to evaluate if the proposal had not
  talked about removing _ObjectiveCBridgeable and replacing it with a
  new ObjectiveCBridgeable protocol, but what could more accurately be
  described as de-underscoring the existing protocol and slightly
  modernizing its API. The existing protocol was always intended to be
  suitable for general use this way, and the design remains
  substantially unchanged, so I support the proposal in principle.

You can blame that on me; After changes from feedback I felt it different enough to call it a replacement.

2. These two APIs should look different IMO:

     init?(bridgedFromObjectiveC: ObjectiveCType)
     init(unconditionallyBridgedFromObjectiveC: ObjectiveCType?)

   they should definitely use “bridging” instead of “bridged,” since the
   phrase describes *how* the conversion is going to be done.

Fair enough, IIRC some of the naming guidelines were still being decided when I wrote it up.
I’m not sure if the proposal needs to go back for revision and review again or if we can make minor changes during review but either way cleaning up the names (and removing the deprecated @warn_unused_result attribute) should be no problem.

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

No. I explicitly call out in the doc that no implicit conversions are allowed except for the automatically generated thunks when importing Objective-C APIs into Swift. You must do an explicit cast in situations where the API can’t import automatically (eg: UnsafePointer<ObjCType>).

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’m not quite following why it breaks the expectations of value or reference semantics. Are you talking about allowing a Swift type to bridge to either a mutable or immutable variant in ObjC? In the proposal as written you’d presumably specify the bridging type as the mutable variant but importing the immutable variant does present a problem (it would violate the ambiguity rules if the types were defined in Swift) - is that what you were driving at?

Russ

···

On Apr 5, 2016, at 1:39 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:


(Russ Bishop) #9

The original intent was to be available on platforms that have an Objective-C runtime (Darwin only at the moment, I guess in theory Windows if someone were determined enough).

You bring up a good point; there is nothing in the protocol that absolutely requires Objective-C, only that it requires AnyObject be the root object type.

However in some hypothetical world where we support importing C++ types, Rust types, or JavaScript (only half trolling) types it would be nice for this protocol to have something tying it to the idea that we are bridging to the Objective-C/corelibs-foundation runtime specifically.

Russ

···

On Apr 5, 2016, at 5:57 PM, Kevin Lundberg via swift-evolution <swift-evolution@swift.org> wrote:

Generally I'm +1 on this, but I do have a concern. It's not made explicit in the proposal, but I presume that this is meant to only be available on Darwin, and not Linux or other platforms that don't have swift using the Objective-C runtime? (Please correct me if I am mistaken.)

I ask because of the swift-corelibs-foundation project; Presumably once that is complete for Swift 3, code that makes use of this type bridging should be able to be run cross-platform. For example:

var a = [AnyObject]()
(a as NSArray).addObject(NSObject()) // should this work on all platforms?

swift-corelibs-foundation uses its own protocol named _ObjectTypeBridgeable (defined here: https://github.com/apple/swift-corelibs-foundation/blob/338f4bf3a89c75a0420b49f5701466e106af02b5/Foundation/NSSwiftRuntime.swift#L205) to simulate what happens today on Darwin platforms, but there is no language support for it so API consumers must explicitly call its bridging methods. It would be great if corelibs-foundation (and any code designed to work on Linux) could take advantage of this proposal to provide the language support, and if the proposal as written is not intended to have an effect on Linux at all, then I would suggest we modify it so that the resulting implementation is not tied to Objective-C and Darwin only.

-Kevin


(Douglas Gregor) #10

  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

The latter. The idea is that the importer sees the bridged type is available and substitutes it on all imported signatures. The actual mechanics of that will involve some generated code (thunk) to call the protocol. I could update the proposal to include what the body of that thunk might look like but it didn’t seem terribly interesting.

Right. There will be some thunking the compiler does.

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

The intention there is that it imports as the bridged type so the latter.

I disagree here: ObjCFizzer is bridged, not ObjCMutableFizzer, so it would be the former.

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

  var myFizzer: Fizzer
  var myMutableFizzer: ObjCMutableFizzer

Is that correct?

No

I would have said “yes" :wink:

* * *

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.

This would not be possible. This sort of bridging only works with special magic types because they are known to always succeed. There is no condition under which Swift will fail to convert String to NSString. The compiler/runtime can’t prove that about any arbitrary type.

We can bridge from, e.g., Fizzer to ObjCFizzer via “as Fizzer” using the entry-point

  func bridgeToObjectiveC() -> ObjectiveCType

For bridging an Objective-C library into Swift, ideally all the APIs will be annotated with SWIFT_BRIDGED so on import the Swift code won’t even be aware the Objective-C type exists. All you’ll see in Swift is the appropriate Swift types. This gives a library (say Photos.framework or UIKit) the chance to provide truly native Swift types by shipping a module with combined Swift and Objective-C code.

Similarly, going the other direction (an app with Objective-C and Swift code) this proposal eliminates the need to deal with the Objective-C types in Swift.

The ObjC types will still exist (unless explicitly banned via NS_UNAVAILABLE_IN_SWIFT or similar), and can leak through in some cases (e.g., UnsafeMutablePointer<ObjCFuzzer>).

  - Doug

···

On Apr 4, 2016, at 9:54 PM, Russ Bishop via swift-evolution <swift-evolution@swift.org> wrote:

On Apr 4, 2016, at 9:22 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:


(Brent Royal-Gordon) #11

(Sorry, sent without finishing a sentence.)

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;

if the conversion always succeeds, though, it will unnecessarily be returning an optional value. (And from what I can tell, you can't satisfy an optional return value in a protocol with a member having a non-optional return value. Not sure why that is.)

···

--
Brent Royal-Gordon
Architechies


(Dave Abrahams) #12

    A few thoughts:

    1. It would have made it easier to evaluate if the proposal had not
    talked about removing _ObjectiveCBridgeable and replacing it with a
    new ObjectiveCBridgeable protocol, but what could more accurately be
    described as de-underscoring the existing protocol and slightly
    modernizing its API. The existing protocol was always intended to be
    suitable for general use this way, and the design remains
    substantially unchanged, so I support the proposal in principle.

You can blame that on me; After changes from feedback I felt it different enough
to call it a replacement.

    2. These two APIs should look different IMO:

    init?(bridgedFromObjectiveC: ObjectiveCType)
    init(unconditionallyBridgedFromObjectiveC: ObjectiveCType?)

    they should definitely use “bridging” instead of “bridged,” since the
    phrase describes *how* the conversion is going to be done.

Fair enough, IIRC some of the naming guidelines were still being decided when I
wrote it up.
I’m not sure if the proposal needs to go back for revision and review again or
if we can make minor changes during review but either way cleaning up the names
(and removing the deprecated @warn_unused_result attribute) should be no
problem.

However it's handled, I think more discussion is needed about these inits.

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

No. I explicitly call out in the doc that no implicit conversions are allowed
except for the automatically generated thunks when importing Objective-C APIs
into Swift. You must do an explicit cast in situations where the API can’t
import automatically (eg: UnsafePointer<ObjCType>).

Great; sorry I missed that.

    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’m not quite following why it breaks the expectations of value or reference
semantics. Are you talking about allowing a Swift type to bridge to either a
mutable or immutable variant in ObjC?

Consider NSPersonNameComponents, a mutable class in Cocoa. If you want
to bridge that to a value type PersonNameComponents, then fine, you can
copy the data. You had better not convert that value type back to
NSPersonNameComponents, though, or you're left with two bad choices:

1. You're converting by extracting an instance of NSPersonNameComponents
   from the implementation of PersonNameComponents, in which case ObjC
   code gets to modify the value of your PersonNameComponents instance.
   That's not value semantics.

2. You're converting by copying the data into a new
   NSPersonNameComponents, in which case ObjC code may have a reasonable
   expectation of being able to meaningfully modify the object and
   seeing that modification reflected somewhere, but it won't be.
   That's not reference semantics.

···

on Wed Apr 06 2016, Russ Bishop <swift-evolution@swift.org> wrote:

    On Apr 5, 2016, at 1:39 PM, Dave Abrahams via swift-evolution > <swift-evolution@swift.org> wrote:

In the proposal as written you’d presumably specify the bridging type
as the mutable variant but importing the immutable variant does
present a problem (it would violate the ambiguity rules if the types
were defined in Swift) - is that what you were driving at?

Russ

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
Dave


(Kevin Lundberg) #13

Good point. I was going to suggest breaking out the protocol into a
general swift version and have an objective-c specific one refine it,
but in the case where bridging to other languages happens, those other
language specific bridging protocols wouldn't be able to refine the
generic base protocol for types that want to bridge more than one
language. I formally retract my concern :slight_smile:

···

On 4/6/2016 1:22 PM, Russ Bishop wrote:

The original intent was to be available on platforms that have an
Objective-C runtime (Darwin only at the moment, I guess in theory
Windows if someone were determined enough).

You bring up a good point; there is nothing in the protocol that
absolutely requires Objective-C, only that it requires AnyObject be
the root object type.

However in some hypothetical world where we support importing C++
types, Rust types, or JavaScript (only half trolling) types it would
be nice for this protocol to have something tying it to the idea that
we are bridging to the Objective-C/corelibs-foundation runtime
specifically.

Russ


(Russ Bishop) #14

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

The intention there is that it imports as the bridged type so the latter.

I disagree here: ObjCFizzer is bridged, not ObjCMutableFizzer, so it would be the former.

Hmmm… So all subclasses are hidden as far as the interface is concerned (and the ambiguity rules)? I’m fine with that, it seems like it suites the class cluster approach just fine.

* * *

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.

This would not be possible. This sort of bridging only works with special magic types because they are known to always succeed. There is no condition under which Swift will fail to convert String to NSString. The compiler/runtime can’t prove that about any arbitrary type.

We can bridge from, e.g., Fizzer to ObjCFizzer via “as Fizzer” using the entry-point

  func bridgeToObjectiveC() -> ObjectiveCType

It does seem strange that this is asymmetrical but I don’t know that it is worth the complexity to introduce an extended protocol to declare a type has a bi-directional always-succeeds bridging conversion.

For bridging an Objective-C library into Swift, ideally all the APIs will be annotated with SWIFT_BRIDGED so on import the Swift code won’t even be aware the Objective-C type exists. All you’ll see in Swift is the appropriate Swift types. This gives a library (say Photos.framework or UIKit) the chance to provide truly native Swift types by shipping a module with combined Swift and Objective-C code.

Similarly, going the other direction (an app with Objective-C and Swift code) this proposal eliminates the need to deal with the Objective-C types in Swift.

The ObjC types will still exist (unless explicitly banned via NS_UNAVAILABLE_IN_SWIFT or similar), and can leak through in some cases (e.g., UnsafeMutablePointer<ObjCFuzzer>).

  - Doug

Agreed, that’s what I meant by "things like performSelector, context objects, …” :slight_smile:

Russ

···

On Apr 5, 2016, at 10:14 AM, Douglas Gregor <dgregor@apple.com> wrote:


(Adrian Kashivskyy) #15

• What is your evaluation of the proposal?

+1, been a supporter of this idea from the very beginning.

• Is the problem being addressed significant enough to warrant a change to Swift?

It is when doing hybrid (Objective-C plus Swift) development.

• Does this proposal fit well with the feel and direction of Swift?

Not with the direction of Swift itself but it certainly helps with the transition from Objective-C.

• How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Been following the topic since the beginning.

–––

One thing,

I noticed the proposal includes @warn_unused_result attribute in one of code examples. As this will be removed per SE-0047, I suggest removing the attribute from the proposal code examples.

Pozdrawiam – Regards,
Adrian Kashivskyy

···

Wiadomość napisana przez Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> w dniu 06.04.2016, o godz. 03:08:

(Sorry, sent without finishing a sentence.)

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;

if the conversion always succeeds, though, it will unnecessarily be returning an optional value. (And from what I can tell, you can't satisfy an optional return value in a protocol with a member having a non-optional return value. Not sure why that is.)

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Douglas Gregor) #16

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

The intention there is that it imports as the bridged type so the latter.

I disagree here: ObjCFizzer is bridged, not ObjCMutableFizzer, so it would be the former.

Hmmm… So all subclasses are hidden as far as the interface is concerned (and the ambiguity rules)? I’m fine with that, it seems like it suites the class cluster approach just fine.

Not “hidden”; they’re accessible and not bridged.

* * *

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.

This would not be possible. This sort of bridging only works with special magic types because they are known to always succeed. There is no condition under which Swift will fail to convert String to NSString. The compiler/runtime can’t prove that about any arbitrary type.

We can bridge from, e.g., Fizzer to ObjCFizzer via “as Fizzer” using the entry-point

  func bridgeToObjectiveC() -> ObjectiveCType

It does seem strange that this is asymmetrical but I don’t know that it is worth the complexity to introduce an extended protocol to declare a type has a bi-directional always-succeeds bridging conversion.

It is a little odd; we have isBridgedToObjectiveC and bridgeToObjectiveC, where we could possibly have the latter produce an ObjectiveCType? and remove the former.

  - Doug

···

On Apr 5, 2016, at 11:36 AM, Russ Bishop <xenadu@gmail.com> wrote:

On Apr 5, 2016, at 10:14 AM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote: