11

I'm starting to get back into Cocoa development after not working on anything for a few months. Originally when I started I was using Snow Leopard and Xcode 3. I'm now running Lion with Xcode 4.2 and I'm running into some issues that I hadn't run into before.

I believe it's probably the fact that I've never used ARC before, so I'm sure I'm missing something.

I'm trying to create Statusbar application, without a main window, or dock icon. When I run the application the Statusbar icon for my application appears briefly, for about a second, but then disappears.

Heres my code.

QuickPlusAppDelegate.h

#import <Cocoa/Cocoa.h>

@interface QuickPlusAppDelegate : NSObject <NSApplicationDelegate>

@property (assign) IBOutlet NSWindow *window;
@property (assign) NSStatusItem *statusItem;
@property (weak) IBOutlet NSMenu *statusItemMenu;

@property (strong) NSImage *statusItemIcon;
@property (strong) NSImage *statusItemIconHighlighted;
@property (strong) NSImage *statusItemIconNewNotification;

@end

QuickPlusAppDelegate.m

#import "QuickPlusAppDelegate.h"

@implementation QuickPlusAppDelegate
@synthesize statusItemMenu = _statusItemMenu;

@synthesize window = _window, statusItem = _statusItem;
@synthesize statusItemIcon = _statusItemIcon, 
    statusItemIconHighlighted = _statusItemIconHighlighted, 
    statusItemIconNewNotification = _statusItemIconNewNotification;

- (void) awakeFromNib
{
    NSBundle *appBundle = [NSBundle mainBundle];
    _statusItemIcon = [[NSImage alloc] initWithContentsOfFile:[appBundle pathForResource:@"statusItemIcon" ofType:@"png"]];
    _statusItemIconHighlighted = [[NSImage alloc] initWithContentsOfFile:[appBundle pathForResource:@"statusItemIconHighlighted" ofType:@"png"]];
    _statusItemIconNewNotification = [[NSImage alloc] initWithContentsOfFile:[appBundle pathForResource:@"statusItemIconNewNotification" ofType:@"png"]];

    _statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
    [_statusItem setImage:_statusItemIcon];
    [_statusItem setAlternateImage:_statusItemIconHighlighted];
    [_statusItem setHighlightMode:YES];
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    // empty
}

@end

Edit If you see anything wrong with my code please let me know. I definitely would some critique so I can get better.

Another Edit It seems as if the Statusbar icon disappears when the main window itself loads.

Brandon Cordell
  • 1,308
  • 1
  • 9
  • 24
  • 1
    Suggestion for your code: Use [appBundle imageForResource:@"statusItemIcon"] instead of your current image loading code. It should be faster, support @2x images transparently, support non-png without code changes, and is easier to read :) – Catfish_Man Jan 15 '12 at 21:19
  • @Catfish_Man Thank You! That's exactly the kind of critique I'm looking for! – Brandon Cordell Jan 15 '12 at 21:25

2 Answers2

22

_statusItem will be autoreleased in this case.

    _statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];

This returns an autoreleased object. _statusItem is just an iVar. Not only that, but you declare the property as assign:

@property (assign) NSStatusItem *statusItem;

What you probably want to do here is make the property strong and then, instead of setting the ivar directly, use the property to set it. So like this:

@property (strong) NSStatusItem *statusItem;

and then:

self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];

That will cause the statusItem be retained. I'm betting that what's happening now is that it gets released when the autorelease pool pops, and then your app crashes the next time anything tries to access it, thus causing it to disappear from the menu bar. Running it through the Zombies instrument would tell you for sure if that was what was happening. But in general, your app needs to have a strong reference to that object for it to stick around.

ipmcc
  • 29,581
  • 5
  • 84
  • 147
  • Thank you. I changed the property to strong and it works great. With it working the way it was intended, should I still remove the iVar and work with the property directly? What would be the benefits of doing it that way? – Brandon Cordell Jan 15 '12 at 21:56
  • 1
    The ivar backs the property. @synthesized properties have generated setter and getter methods that handle the memory management requirements that could be overridden in a subclass. In general, I'd say use the setter/getter methods anywhere other than -init and -dealloc, unless there was a specific reason not to (like perhaps performance i.e. tight loops). That said, ARC should be able to infer the memory management behavior of a synthesized ivar from its property declaration, so using the ivar directly should also probably work, assuming the property was declared `strong`. – ipmcc Jan 15 '12 at 22:06
  • 1
    In the pre-ARC days, you would have had to use the setter/getters to get the memory management behavior "for free." When accessing the ivar directly you would be expected to provide equivalent memory management behaviors manually. I still work primarily with non-ARC codebases, so that's where my initial comment came from. – ipmcc Jan 15 '12 at 22:08
2

I was having this issue in Xamarin. For awhile it worked fine. Then I added extra code to the FinishedLaunching method and the StatusItem started vanishing. I had this code generating the StatusItem:

    public override void AwakeFromNib ()
    {
        var statusItem = NSStatusBar.SystemStatusBar.CreateStatusItem (30);
        statusItem.Menu = mainMenu;
        statusItem.Image = NSImage.ImageNamed ("menuicon");
        statusItem.AlternateImage = NSImage.ImageNamed ("menuicon_selected");
        statusItem.HighlightMode = true;
    }

Eventually, I found my problem. In my Xcode I had declared this property in my AppDelegate but I wasn't using it:

@property(nonatomic, retain) IBOutlet NSStatusItem *statusItem;

When I removed the var the StatusItem continued to show in its infinite glory :)

    public override void AwakeFromNib ()
    {
        statusItem = NSStatusBar.SystemStatusBar.CreateStatusItem (30);
        statusItem.Menu = mainMenu;
        statusItem.Image = NSImage.ImageNamed ("menuicon");
        statusItem.AlternateImage = NSImage.ImageNamed ("menuicon_selected");
        statusItem.HighlightMode = true;
    }

I didn't have to change it to (strong). In fact I tried but it didn't persist when copying back to Xamarin Studio.

2Yootz
  • 3,971
  • 1
  • 36
  • 31