Proposal: Add public(objc) modifier


(Lily Ballard) #1

When writing ObjC code, there's a macro NS_REFINED_FOR_SWIFT (or __attribute__((swift_private))) that mangles the name when imported into Swift. The intention here is to hide an API from normal Swift usage (but still make it callable) so that a better Swift API can be written around it.

But there's no facility for doing this in reverse. You can expose Swift APIs to ObjC, but the API has to be ObjC-compatible. Which means that if you have a non-ObjC-compatible API, you have to write a separate API to expose it to ObjC, and this separate API now shows up in the Swift API too.

I think the simplest way to fix this is to allow for a modifier public(objc) (following the syntax of private(set)). The only thing this modifier does is ensure the declaration gets added to the generated header as if it were public (or—for apps—internal). If the declaration is not marked as @objc then it would emit an error (it could imply @objc but it feels weird to me to have an access control modifier do that). If the declaration is already public (but not internal, so the same source can be used in apps and libraries) it will emit a warning.

Armed with this new modifier, I can start to write code like

class MyClass: NSObject {
func foo<T>(x: T) { ... }
}

extension MyClass {
@objc(foo) public(objc) private func __foo(x: AnyObject) { ... }
}

When used on a property that already has private(set), the public(objc) modifier will only apply to the getter (e.g. the private(set) takes precedence for the setter).

Alternatives:

* Add a new attribute @export_objc that has the same behavior.

* Change the @objc attribute to take an optional second argument, which may be the token "exported", as in @objc(foo,exported). When using the "exported" token, the selector portion is required. We could possibly support an empty selector to indicate the default, as in @objc(,exported), but that looks odd.

My preference is for public(objc) as proposed as it matches more closely with the intended behavior, which is "this API is private in Swift and public in ObjC".

-Kevin Ballard


(Lily Ballard) #2

Proposal PR submitted as https://github.com/apple/swift-evolution/pull/85

-Kevin Ballard

···

On Tue, Dec 15, 2015, at 11:18 AM, Kevin Ballard wrote:

When writing ObjC code, there's a macro NS_REFINED_FOR_SWIFT (or __attribute__((swift_private))) that mangles the name when imported into Swift. The intention here is to hide an API from normal Swift usage (but still make it callable) so that a better Swift API can be written around it.

But there's no facility for doing this in reverse. You can expose Swift APIs to ObjC, but the API has to be ObjC-compatible. Which means that if you have a non-ObjC-compatible API, you have to write a separate API to expose it to ObjC, and this separate API now shows up in the Swift API too.

I think the simplest way to fix this is to allow for a modifier public(objc) (following the syntax of private(set)). The only thing this modifier does is ensure the declaration gets added to the generated header as if it were public (or—for apps—internal). If the declaration is not marked as @objc then it would emit an error (it could imply @objc but it feels weird to me to have an access control modifier do that). If the declaration is already public (but not internal, so the same source can be used in apps and libraries) it will emit a warning.

Armed with this new modifier, I can start to write code like

class MyClass: NSObject {
func foo<T>(x: T) { ... }
}

extension MyClass {
@objc(foo) public(objc) private func __foo(x: AnyObject) { ... }
}

When used on a property that already has private(set), the public(objc) modifier will only apply to the getter (e.g. the private(set) takes precedence for the setter).

Alternatives:

* Add a new attribute @export_objc that has the same behavior.

* Change the @objc attribute to take an optional second argument, which may be the token "exported", as in @objc(foo,exported). When using the "exported" token, the selector portion is required. We could possibly support an empty selector to indicate the default, as in @objc(,exported), but that looks odd.

My preference is for public(objc) as proposed as it matches more closely with the intended behavior, which is "this API is private in Swift and public in ObjC".

-Kevin Ballard


(Lily Ballard) #3

I could really use some feedback on this proposal. This is something that annoys me every time I write a pure-Swift API with an Obj-C wrapper (which, coincidentally, I'm doing again right at this moment). I'd really like to have some graceful way of handling this in Swift so the Obj-C compatibility API is not exposed to Swift clients, but this proposal can't move forward without community support.

-Kevin Ballard

···

On Tue, Dec 15, 2015, at 11:18 AM, Kevin Ballard wrote:

When writing ObjC code, there's a macro NS_REFINED_FOR_SWIFT (or __attribute__((swift_private))) that mangles the name when imported into Swift. The intention here is to hide an API from normal Swift usage (but still make it callable) so that a better Swift API can be written around it.

But there's no facility for doing this in reverse. You can expose Swift APIs to ObjC, but the API has to be ObjC-compatible. Which means that if you have a non-ObjC-compatible API, you have to write a separate API to expose it to ObjC, and this separate API now shows up in the Swift API too.

I think the simplest way to fix this is to allow for a modifier public(objc) (following the syntax of private(set)). The only thing this modifier does is ensure the declaration gets added to the generated header as if it were public (or—for apps—internal). If the declaration is not marked as @objc then it would emit an error (it could imply @objc but it feels weird to me to have an access control modifier do that). If the declaration is already public (but not internal, so the same source can be used in apps and libraries) it will emit a warning.

Armed with this new modifier, I can start to write code like

class MyClass: NSObject {
func foo<T>(x: T) { ... }
}

extension MyClass {
@objc(foo) public(objc) private func __foo(x: AnyObject) { ... }
}

When used on a property that already has private(set), the public(objc) modifier will only apply to the getter (e.g. the private(set) takes precedence for the setter).

Alternatives:

* Add a new attribute @export_objc that has the same behavior.

* Change the @objc attribute to take an optional second argument, which may be the token "exported", as in @objc(foo,exported). When using the "exported" token, the selector portion is required. We could possibly support an empty selector to indicate the default, as in @objc(,exported), but that looks odd.

My preference is for public(objc) as proposed as it matches more closely with the intended behavior, which is "this API is private in Swift and public in ObjC".

-Kevin Ballard


(plx) #4

Some quick, belated feedback: on the one hand I have been in the same situation and remember thinking something like this would be nice.

On the other hand, I did go back and look, and in practice at least for what I was doing in these scenarios was:

- having an objective-c API designed first (even if just a protocol)
- writing a richer, pure-swift implementation,
- making it usable from objective-c (e.g. by adopting that protocol)

…which meant the actual methods were often named different, and had different semantics, and generally wouldn’t really fit into this proposal (hard to imagine accidentally using the objectice-c variants…the semantics are different).

I don’t think I’ve ever “retroactively" tried to expose a cut-down form of Swift code to Objective-C.

Can you provide an example that is named something more-realistic than `foo`?

···

On Dec 15, 2015, at 1:18 PM, Kevin Ballard via swift-evolution <swift-evolution@swift.org> wrote:

When writing ObjC code, there's a macro NS_REFINED_FOR_SWIFT (or __attribute__((swift_private))) that mangles the name when imported into Swift. The intention here is to hide an API from normal Swift usage (but still make it callable) so that a better Swift API can be written around it.

But there's no facility for doing this in reverse. You can expose Swift APIs to ObjC, but the API has to be ObjC-compatible. Which means that if you have a non-ObjC-compatible API, you have to write a separate API to expose it to ObjC, and this separate API now shows up in the Swift API too.

I think the simplest way to fix this is to allow for a modifier public(objc) (following the syntax of private(set)). The only thing this modifier does is ensure the declaration gets added to the generated header as if it were public (or—for apps—internal). If the declaration is not marked as @objc then it would emit an error (it could imply @objc but it feels weird to me to have an access control modifier do that). If the declaration is already public (but not internal, so the same source can be used in apps and libraries) it will emit a warning.

Armed with this new modifier, I can start to write code like

class MyClass: NSObject {
    func foo<T>(x: T) { ... }
}

extension MyClass {
    @objc(foo) public(objc) private func __foo(x: AnyObject) { ... }
}

When used on a property that already has private(set), the public(objc) modifier will only apply to the getter (e.g. the private(set) takes precedence for the setter).

Alternatives:

* Add a new attribute @export_objc that has the same behavior.

* Change the @objc attribute to take an optional second argument, which may be the token "exported", as in @objc(foo,exported). When using the "exported" token, the selector portion is required. We could possibly support an empty selector to indicate the default, as in @objc(,exported), but that looks odd.

My preference is for public(objc) as proposed as it matches more closely with the intended behavior, which is "this API is private in Swift and public in ObjC".

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


(Félix Cloutier) #5

.NET has an EditorBrowsable <https://msdn.microsoft.com/en-us/library/system.componentmodel.editorbrowsableattribute(v=vs.110).aspx> attribute that controls whether things appear in autocomplete or not. I think that this alternative deserves some consideration.

It clearly won't have identical semantics, but I think that it would be a little more graceful and much less involved. This solution only needs a new attribute and SourceKit changes.

Félix

···

Le 5 janv. 2016 à 15:23:55, Kevin Ballard via swift-evolution <swift-evolution@swift.org> a écrit :

Proposal PR submitted as https://github.com/apple/swift-evolution/pull/85

-Kevin Ballard

On Tue, Dec 15, 2015, at 11:18 AM, Kevin Ballard wrote:

When writing ObjC code, there's a macro NS_REFINED_FOR_SWIFT (or __attribute__((swift_private))) that mangles the name when imported into Swift. The intention here is to hide an API from normal Swift usage (but still make it callable) so that a better Swift API can be written around it.

But there's no facility for doing this in reverse. You can expose Swift APIs to ObjC, but the API has to be ObjC-compatible. Which means that if you have a non-ObjC-compatible API, you have to write a separate API to expose it to ObjC, and this separate API now shows up in the Swift API too.

I think the simplest way to fix this is to allow for a modifier public(objc) (following the syntax of private(set)). The only thing this modifier does is ensure the declaration gets added to the generated header as if it were public (or—for apps—internal). If the declaration is not marked as @objc then it would emit an error (it could imply @objc but it feels weird to me to have an access control modifier do that). If the declaration is already public (but not internal, so the same source can be used in apps and libraries) it will emit a warning.

Armed with this new modifier, I can start to write code like

class MyClass: NSObject {
    func foo<T>(x: T) { ... }
}

extension MyClass {
    @objc(foo) public(objc) private func __foo(x: AnyObject) { ... }
}

When used on a property that already has private(set), the public(objc) modifier will only apply to the getter (e.g. the private(set) takes precedence for the setter).

Alternatives:

* Add a new attribute @export_objc that has the same behavior.

* Change the @objc attribute to take an optional second argument, which may be the token "exported", as in @objc(foo,exported). When using the "exported" token, the selector portion is required. We could possibly support an empty selector to indicate the default, as in @objc(,exported), but that looks odd.

My preference is for public(objc) as proposed as it matches more closely with the intended behavior, which is "this API is private in Swift and public in ObjC".

-Kevin Ballard

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


(Andrew Bennett) #6

+1 I've had similar issues, I've had to provide weaker Objective-C
interface because my strong swift interfaces were incompatible with
Objective-C.

I've considered whether @objc(foo) implies public(objc) (in most cases),
and if you'd need private(objc) if it did, but I think your proposal is
more consistent/clear.

···

On Tue, Jan 12, 2016 at 9:51 AM, Kevin Ballard via swift-evolution < swift-evolution@swift.org> wrote:

I could really use some feedback on this proposal. This is something that
annoys me every time I write a pure-Swift API with an Obj-C wrapper (which,
coincidentally, I'm doing again right at this moment). I'd really like to
have some graceful way of handling this in Swift so the Obj-C compatibility
API is not exposed to Swift clients, but this proposal can't move forward
without community support.

-Kevin Ballard

On Tue, Dec 15, 2015, at 11:18 AM, Kevin Ballard wrote:
> When writing ObjC code, there's a macro NS_REFINED_FOR_SWIFT (or
__attribute__((swift_private))) that mangles the name when imported into
Swift. The intention here is to hide an API from normal Swift usage (but
still make it callable) so that a better Swift API can be written around it.
>
> But there's no facility for doing this in reverse. You can expose Swift
APIs to ObjC, but the API has to be ObjC-compatible. Which means that if
you have a non-ObjC-compatible API, you have to write a separate API to
expose it to ObjC, and this separate API now shows up in the Swift API too.
>
> I think the simplest way to fix this is to allow for a modifier
public(objc) (following the syntax of private(set)). The only thing this
modifier does is ensure the declaration gets added to the generated header
as if it were public (or—for apps—internal). If the declaration is not
marked as @objc then it would emit an error (it could imply @objc but it
feels weird to me to have an access control modifier do that). If the
declaration is already public (but not internal, so the same source can be
used in apps and libraries) it will emit a warning.
>
> Armed with this new modifier, I can start to write code like
>
> class MyClass: NSObject {
> func foo<T>(x: T) { ... }
> }
>
> extension MyClass {
> @objc(foo) public(objc) private func __foo(x: AnyObject) { ... }
> }
>
> When used on a property that already has private(set), the public(objc)
modifier will only apply to the getter (e.g. the private(set) takes
precedence for the setter).
>
> Alternatives:
>
> * Add a new attribute @export_objc that has the same behavior.
>
> * Change the @objc attribute to take an optional second argument, which
may be the token "exported", as in @objc(foo,exported). When using the
"exported" token, the selector portion is required. We could possibly
support an empty selector to indicate the default, as in @objc(,exported),
but that looks odd.
>
> My preference is for public(objc) as proposed as it matches more closely
with the intended behavior, which is "this API is private in Swift and
public in ObjC".
>
> -Kevin Ballard
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Lily Ballard) #7

Even if you design the Obj-C API first, if you're implementing it in Swift, you still probably don't want to expose them to Swift if you don't have to. Sure, they may be named differently, but people can still call them, especially if they already used the methods from Obj-C.

As an example, if I have a class FooManager, with a rich strongly-typed Swift API, and a cut-down AnyObject-typed Obj-C API, anyone touching the FooManager from Swift and autocompleting methods would see both sets of methods and wouldn't necessarily know which ones they're supposed to use.

In the code I'm writing right now, I designed the API with a focus on Swift, but I made decisions to allow for Obj-C compatibility (e.g. using classes inheriting from NSObject instead of pure-Swift classes or even structs). Most of the Swift API is not Obj-C-compatible because it's using Swift-only features, so I'm going to have to write Obj-C equivalents for most of my public methods. I really do not want anyone using my half-dozen classes from Swift to see a bunch of Obj-C methods in the autocomplete, because I _know_ someone who's not familiar with the code is going to end up calling the Obj-C API instead of the better Swift API.

-Kevin Ballard

···

On Thu, Jan 14, 2016, at 11:41 AM, plx via swift-evolution wrote:

Some quick, belated feedback: on the one hand I have been in the same situation and remember thinking something like this would be nice.

On the other hand, I did go back and look, and in practice at least for what I was doing in these scenarios was:

- having an objective-c API designed first (even if just a protocol)
- writing a richer, pure-swift implementation,
- making it usable from objective-c (e.g. by adopting that protocol)

…which meant the actual methods were often named different, and had different semantics, and generally wouldn’t really fit into this proposal (hard to imagine accidentally using the objectice-c variants…the semantics are different).

I don’t think I’ve ever “retroactively" tried to expose a cut-down form of Swift code to Objective-C.

Can you provide an example that is named something more-realistic than `foo`?

> On Dec 15, 2015, at 1:18 PM, Kevin Ballard via swift-evolution <swift-evolution@swift.org> wrote:
>
> When writing ObjC code, there's a macro NS_REFINED_FOR_SWIFT (or __attribute__((swift_private))) that mangles the name when imported into Swift. The intention here is to hide an API from normal Swift usage (but still make it callable) so that a better Swift API can be written around it.
>
> But there's no facility for doing this in reverse. You can expose Swift APIs to ObjC, but the API has to be ObjC-compatible. Which means that if you have a non-ObjC-compatible API, you have to write a separate API to expose it to ObjC, and this separate API now shows up in the Swift API too.
>
> I think the simplest way to fix this is to allow for a modifier public(objc) (following the syntax of private(set)). The only thing this modifier does is ensure the declaration gets added to the generated header as if it were public (or—for apps—internal). If the declaration is not marked as @objc then it would emit an error (it could imply @objc but it feels weird to me to have an access control modifier do that). If the declaration is already public (but not internal, so the same source can be used in apps and libraries) it will emit a warning.
>
> Armed with this new modifier, I can start to write code like
>
> class MyClass: NSObject {
> func foo<T>(x: T) { ... }
> }
>
> extension MyClass {
> @objc(foo) public(objc) private func __foo(x: AnyObject) { ... }
> }
>
> When used on a property that already has private(set), the public(objc) modifier will only apply to the getter (e.g. the private(set) takes precedence for the setter).
>
> Alternatives:
>
> * Add a new attribute @export_objc that has the same behavior.
>
> * Change the @objc attribute to take an optional second argument, which may be the token "exported", as in @objc(foo,exported). When using the "exported" token, the selector portion is required. We could possibly support an empty selector to indicate the default, as in @objc(,exported), but that looks odd.
>
> My preference is for public(objc) as proposed as it matches more closely with the intended behavior, which is "this API is private in Swift and public in ObjC".
>
> -Kevin Ballard
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

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


(Lily Ballard) #8

Code completion aside, it just makes me really unhappy to expose a Swift
API that nobody should *ever* call from Swift. I just don't want to have
that API be accessible at all when it only exists to serve as the Obj-C
entrypoint.

-Kevin Ballard

···

On Tue, Jan 5, 2016, at 02:55 PM, Félix Cloutier wrote:

.NET has an EditorBrowsable[1] attribute that controls whether things
appear in autocomplete or not. I think that this alternative deserves
some consideration.

It clearly won't have identical semantics, but I think that it would
be a little more graceful and much less involved. This solution only
needs a new attribute and SourceKit changes.

Félix

Le 5 janv. 2016 à 15:23:55, Kevin Ballard via swift-evolution <swift- >> evolution@swift.org> a écrit :

Proposal PR submitted as
https://github.com/apple/swift-evolution/pull/85

-Kevin Ballard

On Tue, Dec 15, 2015, at 11:18 AM, Kevin Ballard wrote:

When writing ObjC code, there's a macro NS_REFINED_FOR_SWIFT (or
__attribute__((swift_private))) that mangles the name when imported
into Swift. The intention here is to hide an API from normal Swift
usage (but still make it callable) so that a better Swift API can be
written around it.

But there's no facility for doing this in reverse. You can expose
Swift APIs to ObjC, but the API has to be ObjC-compatible. Which
means that if you have a non-ObjC-compatible API, you have to write
a separate API to expose it to ObjC, and this separate API now shows
up in the Swift API too.

I think the simplest way to fix this is to allow for a modifier
public(objc) (following the syntax of private(set)). The only thing
this modifier does is ensure the declaration gets added to the
generated header as if it were public (or—for apps—internal). If the
declaration is not marked as @objc then it would emit an error (it
could imply @objc but it feels weird to me to have an access control
modifier do that). If the declaration is already public (but not
internal, so the same source can be used in apps and libraries) it
will emit a warning.

Armed with this new modifier, I can start to write code like

class MyClass: NSObject { func foo<T>(x: T) { ... } }

extension MyClass { @objc(foo) public(objc) private func __foo(x:
AnyObject) { ... } }

When used on a property that already has private(set), the
public(objc) modifier will only apply to the getter (e.g. the
private(set) takes precedence for the setter).

Alternatives:

* Add a new attribute @export_objc that has the same behavior.

* Change the @objc attribute to take an optional second argument,
  which may be the token "exported", as in @objc(foo,exported). When
  using the "exported" token, the selector portion is required. We
  could possibly support an empty selector to indicate the default,
  as in @objc(,exported), but that looks odd.

My preference is for public(objc) as proposed as it matches more
closely with the intended behavior, which is "this API is private in
Swift and public in ObjC".

-Kevin Ballard

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

Links:

  1. https://msdn.microsoft.com/en-us/library/system.componentmodel.editorbrowsableattribute(v=vs.110).aspx


(Félix Cloutier) #9

I can't agree to that. If I gradually port an API from Objective-C to Swift and at some point decide to go back to make a Swiftier entry point, I'll be very disappointed that I need to change the call sites to the Objective-C-compatible one, not because something will break, but because the Swift mailing list thought Swift would be purer if I couldn't use it.

Félix

···

Le 5 janv. 2016 à 17:56:42, Kevin Ballard <kevin@sb.org> a écrit :

Code completion aside, it just makes me really unhappy to expose a Swift API that nobody should ever call from Swift. I just don't want to have that API be accessible at all when it only exists to serve as the Obj-C entrypoint.

-Kevin Ballard

On Tue, Jan 5, 2016, at 02:55 PM, Félix Cloutier wrote:

.NET has an EditorBrowsable <https://msdn.microsoft.com/en-us/library/system.componentmodel.editorbrowsableattribute(v=vs.110).aspx> attribute that controls whether things appear in autocomplete or not. I think that this alternative deserves some consideration.

It clearly won't have identical semantics, but I think that it would be a little more graceful and much less involved. This solution only needs a new attribute and SourceKit changes.

Félix

Le 5 janv. 2016 à 15:23:55, Kevin Ballard via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

Proposal PR submitted as https://github.com/apple/swift-evolution/pull/85

-Kevin Ballard

On Tue, Dec 15, 2015, at 11:18 AM, Kevin Ballard wrote:

When writing ObjC code, there's a macro NS_REFINED_FOR_SWIFT (or __attribute__((swift_private))) that mangles the name when imported into Swift. The intention here is to hide an API from normal Swift usage (but still make it callable) so that a better Swift API can be written around it.

But there's no facility for doing this in reverse. You can expose Swift APIs to ObjC, but the API has to be ObjC-compatible. Which means that if you have a non-ObjC-compatible API, you have to write a separate API to expose it to ObjC, and this separate API now shows up in the Swift API too.

I think the simplest way to fix this is to allow for a modifier public(objc) (following the syntax of private(set)). The only thing this modifier does is ensure the declaration gets added to the generated header as if it were public (or—for apps—internal). If the declaration is not marked as @objc then it would emit an error (it could imply @objc but it feels weird to me to have an access control modifier do that). If the declaration is already public (but not internal, so the same source can be used in apps and libraries) it will emit a warning.

Armed with this new modifier, I can start to write code like

class MyClass: NSObject {
    func foo<T>(x: T) { ... }
}

extension MyClass {
    @objc(foo) public(objc) private func __foo(x: AnyObject) { ... }
}

When used on a property that already has private(set), the public(objc) modifier will only apply to the getter (e.g. the private(set) takes precedence for the setter).

Alternatives:

* Add a new attribute @export_objc that has the same behavior.

* Change the @objc attribute to take an optional second argument, which may be the token "exported", as in @objc(foo,exported). When using the "exported" token, the selector portion is required. We could possibly support an empty selector to indicate the default, as in @objc(,exported), but that looks odd.

My preference is for public(objc) as proposed as it matches more closely with the intended behavior, which is "this API is private in Swift and public in ObjC".

-Kevin Ballard

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


(Christopher Rogers) #10

I've definitely wanted a feature like this. I hesitate to mention this
because it might be a bug, but you can actually you can do this now. Just
add

@objc
@available(*, unavailable)

and it won't be callable from Swift, while still getting bridged. (I'm not
sure you need @objc but it serves as light documentation anyway.) The buggy
part is the fact that it doesn't get bridged with the NS_UNAVAILABLE
attribute. (It doesn't make much sense to have unavailable code in
non-public APIs though so it may be correct behavior.) I use it a lot when
porting code over to Swift to make sure people don't use the same old
methods and classes they're used to using while providing a way to point
them to the newer API by adding a message to the availability attribute.

···

On Tue, Jan 12, 2016 at 8:40 AM Andrew Bennett via swift-evolution < swift-evolution@swift.org> wrote:

+1 I've had similar issues, I've had to provide weaker Objective-C
interface because my strong swift interfaces were incompatible with
Objective-C.

I've considered whether @objc(foo) implies public(objc) (in most cases),
and if you'd need private(objc) if it did, but I think your proposal is
more consistent/clear.

On Tue, Jan 12, 2016 at 9:51 AM, Kevin Ballard via swift-evolution < > swift-evolution@swift.org> wrote:

I could really use some feedback on this proposal. This is something that
annoys me every time I write a pure-Swift API with an Obj-C wrapper (which,
coincidentally, I'm doing again right at this moment). I'd really like to
have some graceful way of handling this in Swift so the Obj-C compatibility
API is not exposed to Swift clients, but this proposal can't move forward
without community support.

-Kevin Ballard

On Tue, Dec 15, 2015, at 11:18 AM, Kevin Ballard wrote:
> When writing ObjC code, there's a macro NS_REFINED_FOR_SWIFT (or
__attribute__((swift_private))) that mangles the name when imported into
Swift. The intention here is to hide an API from normal Swift usage (but
still make it callable) so that a better Swift API can be written around it.
>
> But there's no facility for doing this in reverse. You can expose Swift
APIs to ObjC, but the API has to be ObjC-compatible. Which means that if
you have a non-ObjC-compatible API, you have to write a separate API to
expose it to ObjC, and this separate API now shows up in the Swift API too.
>
> I think the simplest way to fix this is to allow for a modifier
public(objc) (following the syntax of private(set)). The only thing this
modifier does is ensure the declaration gets added to the generated header
as if it were public (or—for apps—internal). If the declaration is not
marked as @objc then it would emit an error (it could imply @objc but it
feels weird to me to have an access control modifier do that). If the
declaration is already public (but not internal, so the same source can be
used in apps and libraries) it will emit a warning.
>
> Armed with this new modifier, I can start to write code like
>
> class MyClass: NSObject {
> func foo<T>(x: T) { ... }
> }
>
> extension MyClass {
> @objc(foo) public(objc) private func __foo(x: AnyObject) { ... }
> }
>
> When used on a property that already has private(set), the public(objc)
modifier will only apply to the getter (e.g. the private(set) takes
precedence for the setter).
>
> Alternatives:
>
> * Add a new attribute @export_objc that has the same behavior.
>
> * Change the @objc attribute to take an optional second argument, which
may be the token "exported", as in @objc(foo,exported). When using the
"exported" token, the selector portion is required. We could possibly
support an empty selector to indicate the default, as in @objc(,exported),
but that looks odd.
>
> My preference is for public(objc) as proposed as it matches more
closely with the intended behavior, which is "this API is private in Swift
and public in ObjC".
>
> -Kevin Ballard
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

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


(Lily Ballard) #11

Interesting, I didn't realize that worked. It does sound like a bug
though; I would have expected the @available attribute to generate an
NS_AVAILABLE() or NS_UNAVAILABLE() annotation on the Obj-C call.

-Kevin Ballard

I've definitely wanted a feature like this. I hesitate to mention this
because it might be a bug, but you can actually you can do this now.
Just add

@objc @available(*, unavailable)

and it won't be callable from Swift, while still getting bridged. (I'm
not sure you need @objc but it serves as light documentation anyway.)
The buggy part is the fact that it doesn't get bridged with the
NS_UNAVAILABLE attribute. (It doesn't make much sense to have
unavailable code in non-public APIs though so it may be correct
behavior.) I use it a lot when porting code over to Swift to make sure
people don't use the same old methods and classes they're used to
using while providing a way to point them to the newer API by adding a
message to the availability attribute. On Tue, Jan 12, 2016 at 8:40 AM

+1 I've had similar issues, I've had to provide weaker Objective-C
interface because my strong swift interfaces were incompatible with
Objective-C.

I've considered whether @objc(foo) implies public(objc) (in most
cases), and if you'd need private(objc) if it did, but I think your
proposal is more consistent/clear.

I could really use some feedback on this proposal. This is something
that annoys me every time I write a pure-Swift API with an Obj-C
wrapper (which, coincidentally, I'm doing again right at this
moment). I'd really like to have some graceful way of handling this
in Swift so the Obj-C compatibility API is not exposed to Swift
clients, but this proposal can't move forward without community
support.

-Kevin Ballard

···

On Mon, Jan 11, 2016, at 10:36 PM, Christopher Rogers wrote:

Andrew Bennett via swift-evolution <swift-evolution@swift.org> wrote:

On Tue, Jan 12, 2016 at 9:51 AM, Kevin Ballard via swift-evolution >> <swift-evolution@swift.org> wrote:

On Tue, Dec 15, 2015, at 11:18 AM, Kevin Ballard wrote:

> When writing ObjC code, there's a macro NS_REFINED_FOR_SWIFT (or
> __attribute__((swift_private))) that mangles the name when
> imported into Swift. The intention here is to hide an API from
> normal Swift usage (but still make it callable) so that a better
> Swift API can be written around it.

But there's no facility for doing this in reverse. You can expose
Swift APIs to ObjC, but the API has to be ObjC-compatible. Which means
that if you have a non-ObjC-compatible API, you have to write a
separate API to expose it to ObjC, and this separate API now shows up
in the Swift API too.

I think the simplest way to fix this is to allow for a modifier
public(objc) (following the syntax of private(set)). The only thing
this modifier does is ensure the declaration gets added to the
generated header as if it were public (or—for apps—internal). If the
declaration is not marked as @objc then it would emit an error (it
could imply @objc but it feels weird to me to have an access control
modifier do that). If the declaration is already public (but not
internal, so the same source can be used in apps and libraries) it
will emit a warning.

Armed with this new modifier, I can start to write code like

class MyClass: NSObject {

func foo<T>(x: T) { ... }

}

extension MyClass {

@objc(foo) public(objc) private func __foo(x: AnyObject) { ... }

}

When used on a property that already has private(set), the
public(objc) modifier will only apply to the getter (e.g. the
private(set) takes precedence for the setter).

Alternatives:

* Add a new attribute @export_objc that has the same behavior.

* Change the @objc attribute to take an optional second argument,
  which may be the token "exported", as in @objc(foo,exported). When
  using the "exported" token, the selector portion is required. We
  could possibly support an empty selector to indicate the default, as
  in @objc(,exported), but that looks odd.

My preference is for public(objc) as proposed as it matches more
closely with the intended behavior, which is "this API is private in
Swift and public in ObjC".

-Kevin Ballard

_______________________________________________

swift-evolution mailing list

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

_______________________________________________

swift-evolution mailing list

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


(Lily Ballard) #12

Why would you need to change the call sites? If you have existing Swift
code that calls these Obj-C entrypoints, you simply wouldn't mark them
as `public(objc)` but would just leave them as, well, public API.

There's nothing about this proposal that *forces* you to use it. You can
continue to expose API to Obj-C the same way you do today. But the
entire point of public(objc) is to expose API to Obj-C *without*
exposing it to Swift as well (because there's already a better API
exposed to Swift).

-Kevin Ballard

···

On Tue, Jan 5, 2016, at 03:15 PM, Félix Cloutier wrote:

I can't agree to that. If I gradually port an API from Objective-C to
Swift and at some point decide to go back to make a Swiftier entry
point, I'll be very disappointed that I need to change the call sites
to the Objective-C-compatible one, not because something will break,
but because the Swift mailing list thought Swift would be purer if I
couldn't use it.

Félix

Le 5 janv. 2016 à 17:56:42, Kevin Ballard <kevin@sb.org> a écrit :

Code completion aside, it just makes me really unhappy to expose a
Swift API that nobody should *ever* call from Swift. I just don't
want to have that API be accessible at all when it only exists to
serve as the Obj-C entrypoint.

-Kevin Ballard

On Tue, Jan 5, 2016, at 02:55 PM, Félix Cloutier wrote:

.NET has an EditorBrowsable[1] attribute that controls whether
things appear in autocomplete or not. I think that this alternative
deserves some consideration.

It clearly won't have identical semantics, but I think that it would
be a little more graceful and much less involved. This solution only
needs a new attribute and SourceKit changes.

Félix

Le 5 janv. 2016 à 15:23:55, Kevin Ballard via swift-evolution <swift- >>>> evolution@swift.org> a écrit :

Proposal PR submitted as
https://github.com/apple/swift-evolution/pull/85

-Kevin Ballard

On Tue, Dec 15, 2015, at 11:18 AM, Kevin Ballard wrote:

When writing ObjC code, there's a macro NS_REFINED_FOR_SWIFT (or
__attribute__((swift_private))) that mangles the name when
imported into Swift. The intention here is to hide an API from
normal Swift usage (but still make it callable) so that a better
Swift API can be written around it.

But there's no facility for doing this in reverse. You can expose
Swift APIs to ObjC, but the API has to be ObjC-compatible. Which
means that if you have a non-ObjC-compatible API, you have to
write a separate API to expose it to ObjC, and this separate API
now shows up in the Swift API too.

I think the simplest way to fix this is to allow for a modifier
public(objc) (following the syntax of private(set)). The only
thing this modifier does is ensure the declaration gets added to
the generated header as if it were public (or—for apps—internal).
If the declaration is not marked as @objc then it would emit an
error (it could imply @objc but it feels weird to me to have an
access control modifier do that). If the declaration is already
public (but not internal, so the same source can be used in apps
and libraries) it will emit a warning.

Armed with this new modifier, I can start to write code like

class MyClass: NSObject { func foo<T>(x: T) { ... } }

extension MyClass { @objc(foo) public(objc) private func
__foo(x: AnyObject) { ... } }

When used on a property that already has private(set), the
public(objc) modifier will only apply to the getter (e.g. the
private(set) takes precedence for the setter).

Alternatives:

* Add a new attribute @export_objc that has the same behavior.

* Change the @objc attribute to take an optional second argument,
  which may be the token "exported", as in @objc(foo,exported).
  When using the "exported" token, the selector portion is
  required. We could possibly support an empty selector to
  indicate the default, as in @objc(,exported), but that looks
  odd.

My preference is for public(objc) as proposed as it matches more
closely with the intended behavior, which is "this API is private
in Swift and public in ObjC".

-Kevin Ballard

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

Links:

  1. https://msdn.microsoft.com/en-us/library/system.componentmodel.editorbrowsableattribute(v=vs.110).aspx