[swift-evolution] Proposal: Add public(objc) modifier

Kevin Ballard kevin at sb.org
Fri Jan 15 13:14:22 CST 2016


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 at 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 at swift.org
> > https://lists.swift.org/mailman/listinfo/swift-evolution
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution


More information about the swift-evolution mailing list