Chatter in Xcode 9 & Swift 4

I am trying to update the code from the book to Xcode 9 & Swift 4. Up till now, I’ve been able to resolve the issues created by the new API & language changes. Here, however, I’ve run into a problem I can’t seem to get past.

When I enter a message then click the Send button, I get an uncaught exception error:

[Chatter.ChatWindowController receiveDidSendMessageNotification:]: unrecognized selector sent to instance 0x604000140580
2017-10-16 22:04:00.815270-0600 Chatter[58936:2048651] [General] An uncaught exception was raised
2017-10-16 22:04:00.815345-0600 Chatter[58936:2048651] [General] -[Chatter.ChatWindowController receiveDidSendMessageNotification:]: unrecognized selector sent to instance 0x604000140580

The log window does not get updated with the message text because the receiveDidSendMessageNotification function never gets called.

Here is my ChatWindowController code:

import Cocoa

class ChatWindowController: NSWindowController {
  
  @objc dynamic var log: NSAttributedString = NSAttributedString(string: "")
  @objc dynamic var message: String?
  
  private let ChatWindowControllerDidSendMessageNotification = "com.bdlcs.Chatter.ChatWindowControllerDidSendMessageNotification"
  private let ChatWindowControllerMessageKey = "com.bdlcs.Chatter.ChatWindowControllerMessageKey"
  
  // NSTextView does not support weak references
  @IBOutlet var textView: NSTextView!
  
  // Mark: - Lifecycle
  
  override var windowNibName: NSNib.Name? {
    return NSNib.Name("ChatWindowController")
  }
  
  override func windowDidLoad() {
    super.windowDidLoad()
    
    let notificationCenter = NotificationCenter.default
    notificationCenter.addObserver(self,
                                   selector: Selector(("receiveDidSendMessageNotification:")),
                                   name: NSNotification.Name(ChatWindowControllerDidSendMessageNotification),
                                   object: nil)
  }
  
  deinit {
    let notificationCenter = NotificationCenter.default
    notificationCenter.removeObserver(self)
  }
  
  // MARK: - Actions
  
  @IBAction func send(sender: AnyObject) {
    sender.window?.endEditing(for: nil)
    if let message = message {
      let userInfo = [ChatWindowControllerMessageKey: message]
      let notificationCenter = NotificationCenter.default
      notificationCenter.post(name: NSNotification.Name(ChatWindowControllerDidSendMessageNotification),
                              object: self,
                              userInfo: userInfo)
    }
    message = ""
  }
  
  // MARK: - Notifications
  
  // ChatWindowControllerDidSendMessageNotification
  func receiveDidSendMessageNotification(note: NSNotification) {
    let mutableLog = log.mutableCopy() as! NSMutableAttributedString
    
    if log.length > 0 {
      mutableLog.append(NSAttributedString(string: "\n"))
    }
    
    let userInfo = note.userInfo! as! [String : String]
    let message = userInfo[ChatWindowControllerMessageKey]!
    
    let logLine = NSAttributedString(string: message)
    mutableLog.append(logLine)
    
    log = mutableLog.copy() as! NSAttributedString
    
    textView.scrollRangeToVisible(NSRange(location: log.length, length: 0))
  }
  
}

I have tried setting the Selector using only one pair of parenthesis but Xcode generates a warning message when only one set is used. Either way, the program still generates the uncaught exception.

I have also tried prepending @objc to the receiveDidSendMessageNotification function definition to no avail.

Does anyone have any ideas about what I’m missing or doing wrong?

Try the #selector (…) construct:

#selector (receiveDidSendMessageNotification (note:))

Thanks Ibex10, that did the trick.

For anyone else following along, I also had to make one more change in the receiveDidSendMessageNotification function. In the last line that scrolls to the last line in the textView, textView is now an optional and needs to be conditionally unwrapped. The line should read textView?.scrollRangeToVisible( NSRange( location: log.length, length: 0)).

There is a short period for textView being not set (contains the nil value) before initialization. So it is defined as an optional type. After initialization has completed, it should never contain nil value. You can always unconditionally unwrap it safely. That is the reason why outlets are always defined as implicitly unwrapped optional. No need to ? under this situation.