Issues with NSTextViewDelegate methods in Swift

So I'm migrating some of my old ObjC code into Swift, and have run into an inexplicable error that's basically stopped me in my tracks. Would love some help or insight:


I have a subclass of NSTextView that in a couple of places needs to call delegate methods (NSTextViewDelegate). When I convert this code from this:


NSArray *options = [self.delegate textView:self completions:@[] forPartialWordRange:_autocompletionRange indexOfSelectedItem:&index];


which works just fine, to this:


guard
    let options = delegate?.textView(self, completions:[], forPartialWordRange:autocompletionRange, indexOfSelectedItem:&index) as? ContextOption
    else { return }


I get a compile-time error at:


let options = delegate?.textView(self, completions:[], forPartialWordRange:autocompletionRange, indexOfSelectedItem:&index) as? ContextOption
                                ^


saying: "Value of optional type '((NSTextView, completions: [String], forPartialWordRange: NSRange, indexOfSelectedItem: UnsafeMutablePointer<Int>) -> [String])?' not unwrapped; did you mean to use '!' or '?'?"


It wants me to unwrap 'textView' in that function call which is obviously not right:


let options = delegate?.textView?(self, completions:[], forPartialWordRange:autocompletionRange!, indexOfSelectedItem:&index) as? ContextOption


Any ideas?

Accepted Reply

Well, it's an optional method of the protocol, so it makes sense you'd have to check it's implemented before calling it.

Replies

Well, it's an optional method of the protocol, so it makes sense you'd have to check it's implemented before calling it.

Huh... I've never seen that syntax before... It's truly ugly. Also, it breaks Xcode's ability to recognize the full function selector as an atomic unit... but it does work. Thanks. Never would have guessed that.

Why is this in particular ugly? It's just an example of optional chaining — which you used earlier in the expression after 'delegate'.


I'm also not sure what you mean by "full function selector", or what's broken. The "?" isn't part of the selector, if you meant that literally.

That's exactly what I mean. The function is:


func textView(_ textView: NSTextView, completions words: [String], forPartialWordRange charRange: NSRange, indexOfSelectedItem index: UnsafeMutablePointer<Int>) -> [String]


or if you'll pardon my pseudo-code-abbreviation:


textView:completions:forPartialWordRange:indexOfSelectedItem:


It's not:


func textView()


So putting the optional unwrap character '?' immediately before the () of the function call cuts off the rest of the selector, both visually and lexically. In fact, it makes it so that Xcode cannot recognize the function as an atomic unit for the purposes of navigation anymore. When I CMD-click on the expression, it thinks there are two expressions there, and offers to look up both:


delegate?.textView?(self, ...
          ^^^^^^^^


and:


delegate?.textView?(self, completions: , forPartialWordRange: , indexOfSelectedItem: )
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


Neither of which leads anywhere, for obvious reasons. Now this is clearly a bug in Xcode, but IMHO, its still kind of ugly. It's not the fact of optional chaining on a method, but the particular syntax that caught me off guard.

So, what is the correct syntax for testing conformance with an optional protocol method without calling the method? The Swift equivalent of?


respondsToSelector:

This works in a playground:


import UIKit

class AppDelegate: NSObject, UIApplicationDelegate
{ }

func showDelegate ()
{
  let delegate1: UIApplicationDelegate? = AppDelegate ()
  let delegate2: UIApplicationDelegate? = nil

  if delegate1?.applicationWillTerminate == nil {
     print ("delegate1 function is nil")
  }

  if delegate2?.applicationWillTerminate == nil {
     print ("delegate2 function is nil")
  }
}

showDelegate ()


That is, in the case of an optional protocol function, the function name is conceptually an optional pointer to the closure, which can be nil. (Normally, a function name is conceptually a pointer to the closure, but it's both non-nil and a literal. That's why Xcode can do better in that case.)

Well, that makes sense in that particular case, but what about the case above, where every single delegate method of NSTextViewDelegate begins:


func textView(textView: NSTextView, ...


So, how do you differentiate the numerous delegate methods?

Well, you can use a playground as well as I can:


import UIKit

class AppDelegate: NSObject, UIApplicationDelegate
{ }

func showDelegate ()
{
  let delegate: UIApplicationDelegate = AppDelegate ()

// if delegate.application == nil {
// print ("1. delegate function is nil")
// }

  if delegate.application(_:willFinishLaunchingWithOptions:) == nil { // got this by autocompletion, not by knowing it
  print ("2. delegate function is nil")
  }

  print (delegate.application(_:willFinishLaunchingWithOptions:).dynamicType)
}

showDelegate ()


produces:


2. delegate function is nil
Optional<(UIApplication, Optional<Dictionary<NSObject, AnyObject>>) -> Bool>


If lines 10-12 are uncommented, the error on line 10 is:


Playground execution failed: MyIOSPlayground.playground:4:5: error: ambiguous use of 'application(_:willFinishLaunchingWithOptions:)'
        if delegate.application == nil {


Note that in a construct like line 18, if you put a "?" after the entire function name, Swift knows you mean the function, and produces an error saying it's already an optional value.


So, really, the issue is that if the function is actually called (where all this started), you can't put the chaining "?" at the end, the place you think is the most natural, because that is taken to apply to the return value. The only other reasonable place is after the initial name part of the function, which is where it actually goes.


This at least has the virtue of making the optional function call syntax consistent with the optional closure variable call syntax (e.g. using an optional completionHandle parameter), except that the closure variable can't be ambiguous in the way that a "literal" function name can.


Note: I tested this with the latest beta of Xcode 7.3, which has the new #selector syntax. It seems possible that the selector-ish construct in lines 14 and 18 wouldn't compile in Xcode 7.2 or earlier.


The other complication here is that optional functions exist only in @objc protocols, and that there is not really an optional type involved in the chaining in that case. Instead, the function selector needs to be checked via 'respondsToSelector:' in the Obj-C runtime. So the Swift syntax is fakery for this case anyway. 🙂