> On Apr 8, 2016, at 8:53 AM, Shawn Erickson <shaw...@gmail.com> wrote:
> 
> I want to reiterate that I have objective-c code, others have objc code, and 
> the cocoa, etc. frameworks have code that depend on optional protocol for 
> things like (but not limited to) delegates. This is of course obvious but 
> what seems to get lost in the discussion is that you can't always replace the 
> non-existence of an implementation of an optional protocol method with a 
> default implementation.
> 
> I have code that probes a delegate when registered and based on the what 
> subset of the optional protocol methods it handles configures its runtime 
> state to optimize itself to that reality. For example it may avoid allocating 
> and maintaining potentially complex state if one or more methods are not 
> implemented by the delegate (since no one is interested in it). If we just 
> blindly provide default  implementation for optional methods then this 
> optimization couldn't take place.
> 
> I know others - including I believe Apple framework code - do similar 
> optimizations based on what methods an object implements.

Just to be very clear (which I think my initial post wasn’t) my proposal does 
*not* break this optimization when a Swift class is conforming to an @objc 
protocol: even if a default is present, it won’t be visible to the Objective-C 
runtime at all.

My proposal *does* make it significantly harder to implement a check for “did 
the type implement this method?”, because one will effectively have to use 
-respondsToSelector:. For Cocoa-defined delegates, that doesn’t matter at all: 
apps generally implement requirements of delegates/data sources, but almost 
never go through the protocol to use those methods/properties. It’s the 
frameworks that do the calling, and of course they’re already using 
-respondsToSelector: checks.

The main effect is in Swift code that uses @objc optionals and tests for the 
absence of an implementation to perform some optimization. The tenor of the 
previous thread seems to indicate that this probably isn’t common, because 
there are probably better ways to model these cases in Swift—whether it’s with 
multiple protocols or something that specifically describes the policy (e.g., 
http://thread.gmane.org/gmane.comp.lang.swift.evolution/13347/focus=13480).


> I think we should maintain the optional concept in support of bridging 
> existing objc code into swift (confined to @objc)... unless a way to bridge 
> things can be defined that avoids the loss of optimization potential I 
> outlined above.

The direction I’m trying to go is not to have half of a feature—something that 
seems like it should be general, but is tied to @objc—in the language. We get a 
very large number of requests to make “optional” work for Swift protocols, 
because it’s a confusing limitation and there is a ton of overlap with default 
implementations. It would be far better to remove the feature.

> 
> Optional protocols don't need to be expanded into Swift itself since I 
> believe alternate methods and patterns exists to solve the same type of need.

Given that you don’t feel that optional requirements need to work in Swift-only 
protocols, and what I’ve said above about compatibility with Cocoa, do you 
still think we need to keep ‘@objc optional’ as a notion in the language?

        - Doug

> 
> -Shawn
> 
> On Thu, Apr 7, 2016 at 5:12 PM Douglas Gregor via swift-evolution 
> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
> Hi all,
> 
> Optional protocol requirements in Swift have the restriction that they only 
> work in @objc protocols, a topic that’s come up a number 
> <http://thread.gmane.org/gmane.comp.lang.swift.devel/1316/focus=8804> of 
> times 
> <http://thread.gmane.org/gmane.comp.lang.swift.evolution/13347/focus=13480>. 
> The start of these threads imply that optional requirements should be 
> available for all protocols in Swift. While this direction is implementable, 
> each time this is discussed there is significant feedback that optional 
> requirements are not a feature we want in Swift. They overlap almost 
> completely with default implementations of protocol requirements, which is a 
> more general feature, and people seem to feel that designs based around 
> default implementations and refactoring of protocol hierarchies are overall 
> better.
> 
> The main concern with removing optional requirements from Swift is their 
> impact on Cocoa: Objective-C protocols, especially for delegates and data 
> sources, make heavy use of optional requirements. Moreover, there are no 
> default implementations for any of these optional requirements: each caller 
> effectively checks for the presence of the method explicitly, and implements 
> its own logic if the method isn’t there.
> 
> A Non-Workable Solution: Import as optional property requirements
> One suggestion that’s come up to map an optional requirement to a property 
> with optional type, were “nil” indicates that the requirement was not 
> satisfied. For example, 
> 
> @protocol NSTableViewDelegate
> @optional
> - (nullable NSView *)tableView:(NSTableView *)tableView 
> viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row;
> - (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row;
> @end
> 
> currently comes in as
> 
> @objc protocol NSTableViewDelegate {
>   optional func tableView(_: NSTableView, viewFor: NSTableColumn, row: Int) 
> -> NSView?
>   optional func tableView(_: NSTableView, heightOfRow: Int) -> CGFloat
> }
> 
> would come in as:
> 
> @objc protocol NSTableViewDelegate {
>   var tableView: ((NSTableView, viewFor: NSTableColumn, row: Int) -> 
> NSView?)? { get }
>   var tableView: ((NSTableView, heightOfRow: Int) -> CGFloat)? { get }
> }
> 
> with a default implementation of “nil” for each. However, this isn’t 
> practical for a number of reasons:
> 
> a) We would end up overloading the property name “tableView” a couple dozen 
> times, which doesn’t actually work.
> 
> b) You can no longer refer to the member with a compound name, e.g., 
> “delegate.tableView(_:viewFor:row:)” no longer works, because the name of the 
> property is “tableView”.
> 
> c) Implementers of the protocol now need to provide a read-only property that 
> returns a closure. So instead of
> 
> class MyDelegate : NSTableViewDelegate {
>   func tableView(_: NSTableView, viewFor: NSTableColumn, row: Int) -> NSView? 
> { … }
> }
> 
> one would have to write something like
> 
> class MyDelegate : NSTableViewDelegate {
>   var tableView: ((NSTableView, viewFor: NSTableColumn, row: Int) -> 
> NSView?)? = {
>     … except you can’t refer to self in here unless you make it lazy ...
>   }
> }
> 
> d) We’ve seriously considered eliminating argument labels on function types, 
> because they’re a complexity in the type system that doesn’t serve much of a 
> purpose.
> 
> One could perhaps work around (a), (b), and (d) by allowing compound 
> (function-like) names like tableView(_:viewFor:row:) for properties, and work 
> around (c) by allowing a method to satisfy the requirement for a read-only 
> property, but at this point you’ve invented more language hacks than the 
> existing @objc-only optional requirements. So, I don’t think there is a 
> solution here.
> 
> Proposed Solution: Caller-side default implementations
> 
> Default implementations and optional requirements differ most on the caller 
> side. For example, let’s use NSTableView delegate as it’s imported today:
> 
> func useDelegate(delegate: NSTableViewDelegate) {
>   if let getView = delegate.tableView(_:viewFor:row:) { // since the 
> requirement is optional, a reference to the method produces a value of 
> optional function type
>     // I can call getView here
>   }
> 
>   if let getHeight = delegate.tableView(_:heightOfRow:) {
>     // I can call getHeight here
>   }
> }
> 
> With my proposal, we’d have some compiler-synthesized attribute (let’s call 
> it @__caller_default_implementation) that gets places on Objective-C optional 
> requirements when they get imported, e.g.,
> 
> @objc protocol NSTableViewDelegate {
>   @__caller_default_implementation func tableView(_: NSTableView, viewFor: 
> NSTableColumn, row: Int) -> NSView?
>   @__caller_default_implementation func tableView(_: NSTableView, 
> heightOfRow: Int) -> CGFloat
> }
> 
> And “optional” disappears from the language. Now, there’s no optionality 
> left, so our useDelegate example tries to just do correct calls:
> 
> func useDelegate(delegate: NSTableViewDelegate) -> NSView? {
>   let view = delegate.tableView(tableView, viewFor: column, row: row)
>   let height = delegate.tableView(tableView, heightOfRow: row)
> }
> 
> Of course, the code above will fail if the actual delegate doesn’t implement 
> both methods. We need some kind of default implementation to fall back on in 
> that case. I propose that the code above produce a compiler error on both 
> lines *unless* there is a “default implementation” visible. So, to make the 
> code above compile without error, one would have to add:
> 
> extension NSTableViewDelegate {
>   @nonobjc func tableView(_: NSTableView, viewFor: NSTableColumn, row: Int) 
> -> NSView? { return nil }
>   
>   @nonobjc func tableView(_: NSTableView, heightOfRow: Int) -> CGFloat { 
> return 17 }
> } 
> 
> Now, the useDelegate example compiles. If the actual delegate implements the 
> optional requirement, we’ll use that implementation. Otherwise, the caller 
> will use the default (Swift-only) implementation it sees. From an 
> implementation standpoint, the compiler would effectively produce the 
> following for the first of these calls:
> 
> if delegate.responds(to: 
> #selector(NSTableViewDelegate.tableView(_:viewFor:row:))) {
>   // call the @objc instance method with the selector 
> tableView:viewForTableColumn:row:
> } else {
>   // call the Swift-only implementation of tableView(_:viewFor:row:) in the 
> protocol extension above
> }
> 
> There are a number of reasons why I like this approach:
> 
> 1) It eliminates the notion of ‘optional’ requirements from the language. For 
> classes that are adopting the NSTableViewDelegate protocol, it is as if these 
> requirements had default implementations.
> 
> 2) Only the callers to these requirements have to deal with the lack of 
> default implementations. This was already the case for optional requirements, 
> so it’s not an extra burden in principle, and it’s generally going to be 
> easier to write one defaulted implementation than deal with it in several 
> different places. Additionally, most of these callers are probably in the 
> Cocoa frameworks, not application code, so the overall impact should be small.
> 
> Thoughts?
> 
>       - Doug
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution 
> <https://lists.swift.org/mailman/listinfo/swift-evolution>

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

Reply via email to