It’s good, but I’d also +1 going even further and removing all implicit @objc.
I don’t really like all the special stuff we do for Obj-C, and I’m in favour of anything which generalises our bridging model (at least the user-facing part) and makes it more predictable. In this case, all entry/exit points from native Swift are clearly marked. Concerns about boilerplate are taken care of by batch-annotating in extensions. If a method in an extension can’t accept the annotation, it should be an error or require explicit suppression via @nonobjc. However, I don’t believe the compiler currently allows overrides to exist in extensions; that would need to change in order to make the idea useful. - Karl > On 5 Jan 2017, at 01:50, Douglas Gregor via swift-evolution > <[email protected]> wrote: > > Hi all, > > Here’s a draft proposal to limit inference of @objc to only those places > where we need it for consistency of the semantic model. It’s in the realm of > things that isn’t *needed* for ABI stability, but if we’re going to make the > source-breaking change here we’d much rather do it in the Swift 4 time-frame > than later. Proposal is at: > > > https://github.com/DougGregor/swift-evolution/blob/objc-inference/proposals/NNNN-objc-inference.md > > <https://github.com/DougGregor/swift-evolution/blob/objc-inference/proposals/NNNN-objc-inference.md> > > Introduction > > One can explicitly write @objc on any Swift declaration that can be expressed > in Objective-C. As a convenience, Swift also infers @objc in a number of > places to improve interoperability with Objective-C and eliminate > boilerplate. This proposal scales back the inference of @objc to only those > cases where the declaration must be available to Objective-C to maintain > semantic coherence of the model, e.g., when overriding an @objc method or > implementing a requirement of an @objcprotocol. Other cases currently > supported (e.g., a method declared in a subclass of NSObject) would no longer > infer @objc, but one could continue to write it explicitly to produce > Objective-C entry points. > > Swift-evolution thread: here > <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160509/017308.html> > > <https://github.com/DougGregor/swift-evolution/tree/objc-inference#motivation>Motivation > > There are several observations motivating this proposal. The first is that > Swift's rules for inference of @objc are fairly baroque, and it is often > unclear to users when @objc will be inferred. This proposal seeks to make the > inference rules more straightforward. The second observation is that it is > fairly easy to write Swift classes that inadvertently cause Objective-C > selector collisions due to overloading, e.g., > > class MyNumber : NSObject { > init(_ int: Int) { } > init(_ double: Double) { } // error: initializer 'init' with Objective-C > selector 'init:' > // conflicts with previous declaration with the same Objective-C > selector > } > The example above also illustrates the third observation, which is that code > following the Swift API Design Guidelines > <https://swift.org/documentation/api-design-guidelines/> will use Swift names > that often translate into very poor Objective-C names that violate the > Objective-C Coding Guidelines for Cocoa > <https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/CodingGuidelines.html>. > Specifically, the Objective-C selectors for the initializers above should > include a noun describing the first argument, e.g., initWithInteger: and > initWithDouble:, which requires explicit @objc annotations anyway: > > class MyNumber : NSObject { > @objc(initWithInteger:) init(_ int: Int) { } > @objc(initWithDouble:) init(_ double: Double) { } > } > The final observation is that there is a cost for each Objective-C entry > point, because the Swift compiler must create a "thunk" method that maps from > the Objective-C calling convention to the Swift calling convention and is > recorded within Objective-C metadata. This increases the size of the binary > (preliminary tests on some Cocoa[Touch] apps found that 6-8% of binary size > was in these thunks alone, some of which are undoubtedly unused), and can > have some impact on load time (the dynamic linker has to sort through the > Objective-C metadata for these thunks). > > > <https://github.com/DougGregor/swift-evolution/tree/objc-inference#proposed-solution>Proposed > solution > > The proposed solution is to limit the inference of @objc to only those places > where it is required for semantic consistency of the programming model. > > > <https://github.com/DougGregor/swift-evolution/tree/objc-inference#constructs-that-still-infer-objc>Constructs > that (still) infer @objc > > Specifically, @objc will continue to be inferred for a declaration when: > > The declaration is an override of an @objc declaration, e.g., > > class Super { > @objc func foo() { } > } > > class Sub : Super { > /* inferred @objc */ > override func foo() { } > } > This inference is required so that Objective-C callers to the method > Super.foo() will appropriately invoke the overriding method Sub.foo(). > > The declaration satisfies a requirement of an @objc protocol, e.g., > > @objc protocol MyDelegate { > func bar() > } > > class MyClass : MyDelegate { > /* inferred @objc */ > func bar() { } > } > This inference is required because anyone calling MyDelegate.bar(), whether > from Objective-C or Swift, will do so via an Objective-C message send, so > conforming to the protocol requires an Objective-C entry point. > > The declaration has the @IBAction or @IBOutlet attribute. This inference is > required because the interaction with Interface Builder occurs entirely > through the Objective-C runtime, and therefore depends on the existence of an > Objective-C entrypoint. > > The declaration has the @NSManaged attribute. This inference is required > because the interaction with CoreData occurs entirely through the Objective-C > runtime, and therefore depends on the existence of an Objective-C entrypoint. > > The list above describes cases where Swift 3 already performs inference of > @objc and will continue to do so if this proposal is accepted. > > > <https://github.com/DougGregor/swift-evolution/tree/objc-inference#dynamic-no-longer-infers-objc>dynamic > no longer infers @objc > > A declaration that is dynamic will no longer infer @objc. For example: > > class MyClass { > dynamic func foo() { } // error: 'dynamic' method must be '@objc' > @objc dynamic func bar() { } // okay > } > This change is intended to separate current implementation limitations from > future language evolution: the current implementation supports dynamic by > always using the Objective-C message send mechanism, allowing replacement of > dynamic implementations via the Objective-C runtime (e.g., class_addMethod > and class_replaceMethod). In the future, it is plausible that the Swift > language and runtime will evolve to support dynamic without relying on the > Objective-C runtime, and it's important that we leave the door open for that > language evolution. > > This change therefore does two things. First, it makes it clear that the > dynamic behavior is tied to the Objective-C runtime. Second, it means that > well-formed Swift 4 code will continue to work in the same way should Swift > gain the ability to provide dynamic without relying on Objective-C: at that > point, the method foo() above will become well-formed, and the method bar() > will continue to work as it does today through the Objective-C runtime. > Indeed, this change is the right way forward even if Swift never supports > dynamic in its own runtime, following the precedent of SE-0070 > <https://github.com/apple/swift-evolution/blob/master/proposals/0070-optional-requirements.md>, > which required the Objective-C-only protocol feature "optional requirements" > to be explicitly marked with @objc. > > > <https://github.com/DougGregor/swift-evolution/tree/objc-inference#nsobject-derived-classes-no-longer-infer-objc>NSObject-derived > classes no longer infer @objc > > A declaration within an NSObject-derived class will no longer infer objc`. > For example: > > class MyClass : NSObject { > func foo() { } // not exposed to Objective-C in Swift 4 > } > This is the only major change of this proposal, because it means that a large > number of methods that Swift 3 would have exposed to Objective-C (and would, > therefore, be callable from Objective-C code in a mixed project) will no > longer be exposed. On the other hand, this is the most unpredictable part of > the Swift 3 model, because such methods infer @objconly when the method can > be expressed in Objective-C. For example: > > extension MyClass { > func bar(param: ObjCClass) { } // exposed to Objective-C in Swift 3; not > exposed by this proposal > func baz(param: SwiftStruct) { } // not exposed to Objective-C > } > With this proposal, neither method specifies @objc nor is either required by > the semantic model to expose an Objective-C entrypoint, so they don't infer > @objc: there is no need to reason about the type of the parameter's > suitability in Objective-C. > > > <https://github.com/DougGregor/swift-evolution/tree/objc-inference#side-benefit-more-reasonable-expectations-for-objc-protocol-extensions>Side > benefit: more reasonable expectations for @objc protocol extensions > > Users are often surprised to realize that extensions of @objc protocols do > not, in fact, produce Objective-C entrypoints: > > @objc protocol P { } > > extension P { > func bar() { } > } > > class C : NSObject { } > > let c = C() > print(c.respondsToSelector("bar")) // prints "false" > The expectation that P.bar() has an Objective-C entry point is set by the > fact that NSObject-derived Swift classes do implicitly create Objective-C > entry points for declarations within class extensions when possible, but > Swift does not (and, practically speaking, cannot) do the same for protocol > extensions. > > A previous mini-proposal discussed here > <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160104/005312.html> > suggested requiring @nonobjc for members of @objc protocol extensions. > However, limiting inference of @objc eliminates the expectation itself, > addressing the problem from a different angle. > > > <https://github.com/DougGregor/swift-evolution/tree/objc-inference#source-compatibility>Source > compatibility > > The two changes that remove inference of @objc are both source-breaking in > different ways. The dynamic change mostly straightforward: > > In Swift 4 mode, introduce an error that when a dynamic declaration does not > explicitly state @objc, with a Fix-It to add the @objc. > > In Swift 3 compatibility mode, continue to infer @objc for dynamic methods. > However, introduce a warning that such code will be ill-formed in Swift 4, > along with a Fix-It to add the @objc. This > > A Swift 3-to-4 migrator could employ the same logic as Swift 3 compatibility > mode to update dynamic declarations appropriately. > > The elimination of inference of @objc for declarations in NSObject subclasses > is more complicated. Considering again the three cases: > > In Swift 4 mode, do not infer @objc for such declarations. Source-breaking > changes that will be introduced include: > > If #selector or #keyPath refers to one such declaration, an error will be > produced on previously-valid code that the declaration is not @objc. In most > cases, a Fix-It will suggest the addition of @objc. > > The lack of @objc means that Objective-C code in mixed-source projects won't > be able to call these declarations. Most problems caused by this will result > in warnings or errors from the Objective-C compiler (due to unrecognized > selectors), but some might only be detected at runtime. These latter cases > will be hard-to-detect. > > Other tools and frameworks that rely on the presence of Objective-C > entrypoints but do not make use of Swift's facilities for referring to them > will fail. This case is particularly hard to diagnose well, and failures of > this sort are likely to cause runtime failures that only the developer can > diagnose and correct. > > In Swift 3 compatibility mode, continue to infer @objc for these > declarations. When @objc is inferred based on this rule, modify the generated > header (i.e., the header used by Objective-C code to call into Swift code) so > that the declaration contains a "deprecated" attribute indicating that the > Swift declaration should be explicitly marked with @objc. For example: > > class MyClass : NSObject { > func foo() { } > } > will produce a generated header that includes: > > @interface MyClass : NSObject > -(void)foo NS_DEPRECATED("MyClass.foo() requires an explicit `@objc` in Swift > 4"); > @end > This way, any reference to that declaration from Objective-C code will > produce a warning about the deprecation. Users can silence the warning by > adding an explicit @objc. > > A Swift 3-to-4 migrator is the hardest part of the story. Ideally, the > migrator to only add @objc in places where it is needed, so that we see some > of the expected benefits of code-size reduction. However, there are two > problems with doing so: > > Some of the uses that imply the need to add @objc come from Objective-C code, > so a Swift 3-to-4 migrator would also need to compile the Objective-C code > (possibly with a modified version of the Objective-C compiler) and match up > the "deprecated" warnings mentioned in the Swift 3 compatibility mode bullet > with Swift declarations. > > The migrator can't reason about dynamically-constructed selectors or the > behavior of other tools that might directly use the Objective-C runtime, so > failing to add a @objc will lead to migrated programs that compile but fail > to execute correctly. > > > <https://github.com/DougGregor/swift-evolution/tree/objc-inference#overriding-of-declarations-introduced-in-class-extensions>Overriding > of declarations introduced in class extensions > > Swift's class model doesn't support overriding of declarations introduced in > class extensions. For example, the following code produces an amusing error > message on the override: > > class MySuperclass { } > > extension MySuperclass { > func extMethod() { } > } > > class MySubclass : MySuperclass { } > > extension MySubclass { > override func extMethod() { } // error: declarations in extensions cannot > override yet > } > However, this does work in Swift 3 when the method is @objc, e.g., > > class MySuperclass { } > > extension MySuperclass { > @objc func extMethod() { } > } > > class MySubclass : MySuperclass { } > > extension MySubclass { > override func extMethod() { } // okay! Objective-C message dispatch > allows this > } > Removing @objc inference for NSObject subclasses will therefore break this > correct Swift 3 code: > > class MySuperclass { } > > extension MySuperclass : NSObject { > func extMethod() { } // implicitly @objc in Swift 3, not @objc in Swift 4 > } > > class MySubclass : MySuperclass { } > > extension MySubclass { > override func extMethod() { } // okay in Swift 3, error in Swift 4: > declarations in extensions cannot override yet > } > There are several potential solutions to this problem, but both are > out-of-scope for this particular proposal: > > Require that a non-@objc declaration in a class extension by explicitly > declared final so that it is clear from the source that this declaration > cannot be overridden. > > Extend Swift's class model to permit overriding of declarations introduced in > extensions. > > > <https://github.com/DougGregor/swift-evolution/tree/objc-inference#alternatives-considered>Alternatives > considered > > Aside from the obvious alternative of "do nothing", there are ways to address > some of the problems called out in theMotivation > <https://github.com/DougGregor/swift-evolution/tree/objc-inference#motivation> > section without eliminating inference in the cases we're talking about, or > to soften the requirements on some constructs. > > > <https://github.com/DougGregor/swift-evolution/tree/objc-inference#mangling-objective-c-selectors>Mangling > Objective-C selectors > > Some of the problems with Objective-C selector collisions could be addressed > by using "mangled" selector names for Swift-defined declarations. For > example, given: > > class MyClass : NSObject { > func print(_ value: Int) { } > } > Instead of choosing the Objective-C selector "print:" by default, which is > likely to conflict, we could use a mangled selector name like > "__MyModule__MyClass__print__Int:" that is unlikely to conflict with anything > else in the program. However, this change would also be source-breaking for > the same reasons that restricting @objc inference is: dynamic behavior that > constructs Objective-C selectors or tools outside of Swift that expect > certain selectors will break at run-time. > > > <https://github.com/DougGregor/swift-evolution/tree/objc-inference#completely-eliminating-objc-inference>Completely > eliminating @objc inference > > Another alternative to this proposal is to go further and completely > eliminate @objc inference. This would simplify the programming model > further---it's exposed to Objective-C only if it's marked @objc---but at the > cost of significantly more boilerplate for applications that use Objective-C > frameworks. For example: > > class Sub : Super { > @objc override func foo() { } // @objc is now required > } > > class MyClass : MyDelegate { > @objc func bar() { } // @objc is now required > } > I believe that this proposal strikes the right balance already, where @objc > is inferred when it's needed to maintain the semantic model, and can be > explicitly added to document those places where the user is intentionally > exposing an Objective-C entrypoint for some reason. Thus, explicitly writing > @objc indicates intent without creating boilerplate. > > > <https://github.com/DougGregor/swift-evolution/tree/objc-inference#proposal-add-on-objc-annotations-on-class-definitions-and-extensions>Proposal > add-on: @objc annotations on class definitions and extensions > > If the annotation burden of @objc introduced by this proposal is too high, we > could make @objc on a class definition or extension thereof imply @objc on > those members that can be exposed to Objective-C. For example: > > @objc extension MyClass { > // implicitly @objc > func f() { } > > // Cannot be exposed to Objective-C because tuples aren't representable in > Objective-C > func g() -> (Int, Int) { return (1, 2) } > } > This would reduce (but not eliminate) the annotation burden introduced by > this proposal, allowing developers to group Objective-C-exposed declarations > together under a single @objc annotation. This reduces the amount of > boilerplate. With such a change, we'd need to decide what to do with > MyClass.g(), which could be either: > > Make it an error because the context implies that it is @objc, but it cannot > be. The error would be suppressed with an explicit @nonobjc annotation. > > Make it implicitly @nonobjc. > > Option (1) seems more in line with the rest of the proposal, because it > maintains a predictable model. Regardless, this add-on makes sense if we like > the benefits of the proposal to limit @objc inference but the annotation > burden turns out to be annoyingly high. > > > - Doug > > > _______________________________________________ > swift-evolution mailing list > [email protected] > https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
