Hi all,

Right now, Swift infers @objc (meaning that you don’t have to write it 
explicitly) in a number of places:

1) When overriding an @objc declaration
2) When implementing a requirement of an @objc protocol (note: this behavior 
was recently enabled in the Swift compiler)
3) When the declaration is‘@IBOutlet’, or ‘@NSManaged’
4) When the declaration is ‘dynamic’
5) When the declaration is non-private and occurs within a subclass of NSObject 
or an extension thereof, so long as it *can* be expressed in Objective-C

There are different motivations behind the various rules, some of which bear 
re-examination. Before going into the individual rules, here’s why I’m bringing 
this up now:

a) There is no easy way to articulate the rules we have. They mostly came from 
a desire to avoid making Swift programmers write ‘@objc’, but that’s not very 
principled.

b) When Swift code follows the Swift API design guidelines, the Objective-C 
names provided by the Swift compiler by default aren’t good names in 
Objective-C, a topic I brought up a few months ago 
<http://thread.gmane.org/gmane.comp.lang.swift.evolution/7733>. The upshot is 
that, for entities that are intended to be used in Objective-C, inferring 
‘@objc’ doesn’t help when you’re going to explicit write out the Objective-C 
selector anyway (‘@objc(objectAtIndex:)’). The Grand Renaming is forcing this 
on Swift programmers already, so it’s a reasonable time to make a change in the 
area of @objc inference.

c) The “thunks” generated for the compiler to provide Objective-C entry points 
have a non-trivial cost. I measured the code size of a handful of Swift 
projects (all Cocoa[Touch] apps, most Swift-only, some mixed 
Swift/Objective-C), and 6-8% of code size was in these @objc thunks. We have 
some ideas to reduce the code size of @objc thunks in general. However,  I 
suspect that most of these @objc thunks are completely unused, although I 
haven’t done the legwork to prove it, and there is no automatic way to remove 
them.

d) Removing @objc inference for a particular case can silently break working 
code, because something invisible to the Swift compiler (e.g., in Objective-C 
in a mixed-source project, in a system framework, etc.) might be depending on 
that Objective-C entry point. We can probably tolerate such breakage in Swift 3 
where we’re breaking lots of things, but our ability to make sure changes here 
will be diminish rapidly over time. Hence, we’re talking about this now, and we 
need to live with our decisions for a long time.

In general, I’d like to remove @objc inference from places where it isn’t 
needed for the “semantic model”, loosely construed. Let’s go case-by-case with 
the places we do inference now:

(1) and (2) are geared toward consistency of the semantic model. For example, 
you need @objc inference for overrides of @objc declarations because otherwise 
Objective-C code won’t see the override and you’ll get divergent behavior. 
Similarly when you are implementing a requirement of an @objc protocol: 
Objective-C callers need to be able to have something to call. I consider it 
critical that we maintain @objc inference for these cases because maintaining a 
consistent semantic model across Swift and Objective-C code is fundamental to 
interoperability. No proposed change here, aside from noting that the ability 
to infer @objc (including the selector names) when implementing requirements of 
an @objc protocol was very recently introduced in the Swift compiler.

(3) is about interoperability with certain frameworks and tools. @objc 
inference helps eliminate some boilerplate today, because these features depend 
on the Objective-C runtime. Will these features, with their current spelling in 
Swift, *always* depend on the Objective-C runtime? If so, we can keep the @objc 
inference. Or is there some probable future where the same features could get 
implementations that don’t rely on the Objective-C runtime? If so, we might 
want to eliminate @objc inference for them: today, that means having to write 
‘@objc’ explicitly on such declarations, and if that future comes to pass the 
requirement to add ‘@objc’ will go away (no existing code will break unless a 
user manually deletes an ‘@objc’ from that code). With ‘@NSManaged’, I feel 
like the underlying feature would look *very* different if implemented without 
a backing Objective-C runtime, so I’m inclined to leave @objc inference in 
place. With ‘@IBOutlet’ it’s a whole lot more murky, and the confusion is 
magnified by the fact that ‘@IBAction’ doesn’t currently have @objc inference. 
We should align IBOutlet and IBAction in this regard (so some change is 
needed), but I don’t have a strong sense of which way to go.

(4) ‘dynamic’ infers ‘@objc’ because the Swift runtime doesn’t have support for 
replacing a method at runtime. There are two possible futures here: Swift’s 
runtime gets support for ‘dynamic’ on pure Swift methods (in which case the 
‘@objc’ inference would become vestigial cruft that would be hard to take 
away), or we decide that ‘dynamic’ is an Objective-C compatibility feature that 
always implies Objective-C. With SE-0070 
<https://github.com/apple/swift-evolution/blob/master/proposals/0070-optional-requirements.md>,
 we started requiring an explicit ‘@objc’ for optional requirements to make it 
clear that this is an Objective-C compatibility feature, so in a sense both 
futures point to requiring explicit ‘@objc’ on ‘dynamic’. Therefore, I propose 
to drop @objc inference for ‘dynamic’ declarations. For now (and maybe 
forever), that means requiring ‘@objc’ to be written along with ‘dynamic’.

(5) was designed to reduce the need for programmers to explicitly write 
‘@objc’. I also suspect it’s the culprit behind many of the unused @objc thunks 
hypothesized in (c). It is useful in mixed-source projects because you get more 
of your Swift APIs accessible to your Objective-C code for free, but it’s 
usefulness in Swift-only projects is far more limited. I propose that we 
eliminate @objc inference for non-private members of NSObject subclasses and 
extensions thereof. It means that one will need to be much more explicit for 
those @objc entry points that are needed (and yes, this will break code), but 
we will eliminate a (probably significant) number of unnecessary @objc entry 
points. It also eliminates reflexively inheriting from NSObject “just because 
it saves typing”, which can help nudge Swift classes toward the more-efficient 
Swift object model. There is a possible middle path here: eliminate the 
inference by default, then add a Swift compiler flag that enables @objc 
inference for these cases. One’s build system would be expected to pass this 
flag for mixed-source projects.

That’s the meat of the proposal. There are two minor follow-ons:

* Allow one to add @objc (or @nonobjc) to an extension, which will apply to all 
members of the extension not explicitly marked @objc or @nonobjc, e.g.,

@objc extension MyClass {
  func anObjCMethod() { }
  var anObjCProperty: String { … }
}

This makes it more convenient to add Objective-C entrypoints to a bunch of 
declarations at once.

* Require one to add @nonobjc to members of extensions of @objc protocols. 
Members of extensions of @objc protocols cannot be @objc, because there’s no 
way to implement this without major surgery in the Objective-C runtime or 
admitting inconsistent behavior (e.g., it only works for subclasses of 
NSObject). Requiring @nonobjc makes it explicit that these members are only 
available in Swift. I’ve proposed this before 
<http://thread.gmane.org/gmane.comp.lang.swift.evolution/2136>, and it didn’t 
really get a whole lot of support, but I think it’s important for clarifying 
the model.

What’s everyone think?

        - Doug

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to