> On Jan 4, 2017, at 9:36 PM, Charlie Monroe <[email protected]> wrote:
> 
> I'm personally coming back and forth on this - my code is now 99% pure Swift, 
> so I don't really need @objc declarations aside for:
> 
> - CoreData - which is fairly obvious and @NSManaged will scream if the type 
> isn't @objc-compatible
> 
> - UI - and this is a big part. Unfortunately, I've relied on @objc inference 
> in multiple cases when it comes to binding (macOS). Or to be more precise 
> @objc implied dynamic. So for NSObject subclasses
> 
> var foo: String = ""
> 
> is without further annotations usable with bindings (though IB screams that 
> NSString is expected and it found String). And who has previously dealt with 
> them knows that this change will be hell to debug and discover all the places 
> since Interface Builder usually isn't very helpful in this matter.
> 
> In order for the migration to go smoothly, it would require the IB to do some 
> kind of keyPath validations and add annotations to those places as well. 
> Without it, I can't imagine the transition... IB has been a source of great 
> deal of ObjC -> Swift headaches, I wish there weren't any more.

>From a migration standpoint, Timothy Wood’s suggestion would properly migrate 
>such code to Swift 4. However, my proposal could make *authoring* such code 
>harder, because you would have to remember to write the @objc… and debugging 
>the failures at runtime could be painful. Maybe there’s some trick in the 
>runtime we could do to provide a better failure when things do go badly here.

        - Doug

> 
> 
>> On Jan 5, 2017, at 1:50 AM, Douglas Gregor via swift-evolution 
>> <[email protected] <mailto:[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] <mailto:[email protected]>
>> https://lists.swift.org/mailman/listinfo/swift-evolution
> 

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

Reply via email to