Failing to Succeed

At OSCON 2006, I gave a talk titled “Failing to Succeed” in which I made a series of observations about how the value of open source software was due to its willingness to acknowledge (and thereby, fix) bugs.   The point being that learning happens in the gap between making an error and correcting it.

I realized that I have neglected to act on this realization.  I’ve been writing blog posts (as so many tutorials do) explaining a solution for the task I’m describing.  But I realized as I was readying a post on creating the status item for ClickSliver, that I spent most of my time figuring out why the thing I was doing wasn’t working — and yet the post was just explaining the solution I eventually settled on, without describing the strange behavior I observed, and what I eventually realized I was doing wrong.

I need to fix that.

Going forward, I intend to spend more time talking about what didn’t work, and what I learned therefrom — and, yes, eventually describing a solution.  Hopefully the description of the failure will be more valuable than the description of the success.  

Having created an icon and customized XCode to create Swift files the way I prefer (my last two posts), it is time to start writing code.

ClickSliver starts as a “status item” — a utility which sits in the Mac menu bar.   So step one is — write the code to create a status item and put it in the menu bar.  I’ll start with a dummy menu — and I find some sample code which does just that.  In the applicationDidFinishLaunching method of my App Delegate, I create the menu to attach to the status item.  

func applicationDidFinishLaunching(_ notification: Notification) {
NSApp.mainMenu = NSMenu()

let mb = NSApp.mainMenu!.addSubmenu(withTitle:
NSLocalizedString("ClickSliver", tableName: "Menu", value: "ClickSliver", comment:"Application name")
)

mb += R0MenuItem(title: NSLocalizedString("About ClickSliver", tableName: "Menu",
             value: "About ClickSliver", comment: "about clicksliver"), keyEquivalent: "n") {_ in 
AboutWindowController.open()

The code shown above won’t make sense yet — I’ll explain it in the next post — but lets assume that the net effect is that the variable mb contains the desired menu.  The next step, then, is to create the status item and attach the menu.   The code looks like:

 let sb = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
let im = NSImage(named: NSImage.Name("AppIcon"))

let x = NSStatusBar.system.thickness - 4
sb.image = im?.resize(withSize: NSSize(width: x, height: x))

sb.menu = mb
sb.isVisible = true

The middle two lines resize the AppIcon to fit in the status item — the resize method comes from R0Kit.  One can see the behavior by just doing 

 sb.image = im

instead of those two middle lines (the icon will be big — but the behavior is the same).  It seems simple enough:

  • create the status item
  • get the image
  • set the image
  • set the menu
  • make it visible

Nothing happens.

Actually, if I run it again and watch closely, I see the icon appear on the menu bar, then disappear.  Definitely.  It appears, then disappears.

Perhaps it needs to be explicitly enabled.  Maybe I need to add 

sb.isEnabled = true

Nope.  No difference in behavior.  I literally copied this code from the sample — how could it not work?

Well, technically I didn’t copy the code, I changed the variable names.  Should that matter?  Does it matter what the variable names are?

This is where most of my programming time goes: matching my mental model of what is going on, and what things matter, with what is actually going on and what actually matters.   I’m pretty sure that the names of the variables don’t matter.   But what if they did?

Nah!  Those of you who have seen this before, and know what is going on can probably spot the error immediately.  It turns out I not only changed the name, but I had to add a “let” in front of the first line in order to create “sb” as a variable — otherwise it wouldn’t even compile.  You see, the sample code was using an instance variable — so it didn’t need a let.  But I didn’t have such an instance variable, so I added the let, and ran the code.  Since the variable is a local variable, it gets garbage collected when the method ends, and when the status item gets garbage collected, it gets removed from the menu bar.  Hence the appear/disappear behavior.

OK.  Explained that way, it makes sense.  Presumably that means if I create a window with a view, any subviews that I add to the view will disappear unless I store them all in persistent instance variables.  Actually, no.  If I create subviews and attach them to a view, then if I retain the view, all the subviews will not be garbage collected — even if I only used local variable references to create them.

Hence my cognitive dissonance.  It looks to me like a status item is kind of like a subview in the menu bar — and as long as the menu bar hasn’t been garbage collected, its subviews won’t be garbage collected either.  Except, apparently, that’s not how it works for the menu bar.  Incidentally, it is how it works for the menus.   Note that mb is a local variable containing the menu.  I attach the menu to the status item, then the local variable containing the menu goes out of scope.  But as long as the status item hangs around, the menu hangs around.

So, when creating UI elements, they will not be garbage collected if they are attached to other UI elements — unless they are status items — in which case they will be garbage collected even if they are attached to the menu bar.

The solution then is, I create an instance variable in my AppDelegate, and stuff sb into that (although the code shown below needs to be in the reverse order in order to work.  Why?  Well, I need to terminate the method in order enter the class scope in order to create the instance variable. )

statusBarItem = sb
}

var statusBarItem : NSStatusItem?

And now the status item remains in the menubar.

I skipped over:

  • creating the menu — that looked weird
  • resizing the image
  • defining the AppDelegate
  • writing the main function

All these will be covered in the next few posts.  But I wanted to explain why I’ll be talking so much more about things that didn’t work — and in particular, the thing that didn’t work which started this whole train of thought.