Beyond scripting in Swift: the popup menu that won’t validate

Most of the time, I use popup menus to set options. Their sole action is to select a value which I read in a function, and they don’t need their own custom actions. But in Consolation 3, I have two popup menus which need to trigger the re-conversion of a log extract in JSON format to the displayed attributed string.

I had long handled that by attaching an IBAction to those popup menus: whenever the user selected a menu item there, this triggered
@IBAction func updateAttrText(_ sender: Any) {
if (self.popupStyle.indexOfSelectedItem > 2) && (self.theJSONDictionaries.count > 0) {
let theStyledStr = self.styleLogStr()
self.theResTxt.textStorage?.setAttributedString(theStyledStr)
} }

swiftmyst01

This checks that the log extract is being held in JSON format (according to the setting in the popupStyle popup menu), and that its JSON dictionaries are available, then generates a new styled attributed string and displays it in theResTxt, within a scrolling text view.

That had worked fine for quite a while. Just recently, though, it started to throw an uncaught exception whenever you clicked on the popup menu:
2018-06-11 07:57:43.583189+0100 Consolation3[23912:17895879] -[Consolation3.ViewController validateMenuItem:]: unrecognized selector sent to instance 0x7fbfb7d09d30
2018-06-11 07:57:43.583399+0100 Consolation3[23912:17895879] [General] An uncaught exception was raised
2018-06-11 07:57:43.583412+0100 Consolation3[23912:17895879] [General] -[Consolation3.ViewController validateMenuItem:]: unrecognized selector sent to instance 0x7fbfb7d09d30
2018-06-11 07:57:43.587488+0100 Consolation3[23912:17895879] [General] (
0 CoreFoundation 0x00007fff807732cb __exceptionPreprocess + 171
1 libobjc.A.dylib 0x00007fff9559148d objc_exception_throw + 48
2 CoreFoundation 0x00007fff807f4f04 -[NSObject(NSObject) doesNotRecognizeSelector:] + 132
3 CoreFoundation 0x00007fff806e554a ___forwarding___ + 538
4 CoreFoundation 0x00007fff806e52a8 _CF_forwarding_prep_0 + 120
5 Consolation3 0x0000000102797085 _T012Consolation314ViewControllerC16validateMenuItemSbSo06NSMenuF0CFTf4gn_n + 165
6 Consolation3 0x00000001027828c9 _T012Consolation314ViewControllerC16validateMenuItemSbSo06NSMenuF0CFTo + 41
7 AppKit 0x00007fff7e522b4d -[NSPopUpButtonCell validateMenuItem:] + 303
8 AppKit 0x00007fff7e36f546 -[NSMenu _enableItem:] + 684

and so on.

Consolation has been undergoing quite a lot of change in its source recently, but I couldn’t understand what was throwing this previously reliable code. Although this raised an exception each time the popup menus were used, the app carried on working just fine; indeed, there was the slightly sardonic touch that a log browser should keep writing into the log.

As might be expected, I wasted quite a lot of time discovering that any IBAction attached to either of the popup menus in question generated the exception, and that attaching an IBAction in this way is ordinarily perfectly good and should work fine. It was then that it occurred to me that a few days ago, I had added an override to the validateMenuItem function in that ViewController. This was to enable three preference-handling menu items which had somehow been left disabled some time ago.

The last case in its switch was to drop any other validateMenuItem calls through to super.validateMenuItem, which should have handled those popups gracefully. What if it didn’t?

The simple solution was to add the IBAction which handles both the popups, updateAttrText, as a selector to validateMenuItem:
override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
switch menuItem.action {
case #selector(ViewController.exportPrefs)?:
return true
case #selector(ViewController.importPrefs)?:
return true
case #selector(ViewController.showPrefs)?:
return true
case #selector(ViewController.updateAttrText)?:
return true
default:
return super.validateMenuItem(menuItem)
} }

swiftmyst02

The exception immediately vanished, and order has been restored.

I’m puzzled as to why this worked without problems before I overrode validateMenuItem, but broke the moment that it relied on the super call in my overriding function. It’s tempting to blame it on Xcode 9, but exactly the same happened in the current beta of Xcode 10, so it looks as if this behaviour is expected, if aberrant to my eye.