Problem with objc2 garbage collection!

Discussion in 'Mac Programming' started by shayanoh, Nov 1, 2007.

  1. macrumors newbie

    Joined:
    Nov 1, 2007
    #1
    Hi all!
    I've done a little cocoa programming before in tiger, and wrote a simple program to manage smileys, it had a few bugs, and some memory leaks! So I decided to rewrite it from scratch in objc2 and use garbage collection, and in process learn to use, and use core-animation and ... in my code.
    Well, now before I even start I hit a problem. I have this code in my controller class to show an icon in status bar.
    Code:
    @interface Controller : NSObject {
    NSStatusItem* theItem; 
    IBOutlet NSMenu *StatusItemMenu;// Properly connected to a menu in IB
    }
    -(void)awakeFromNib;
    @end
    
    @implementation Controller
    -(void)awakeFromNib
    {
    	NSStatusBar *bar = [NSStatusBar systemStatusBar];
    	
    	theItem = [bar statusItemWithLength:NSSquareStatusItemLength];
    	[theItem setHighlightMode:YES];
    	[theItem setImage:[NSImage imageNamed:@"MenuIcon"]];
    	[theItem setMenu:StatusItemMenu];
    }
    @end
    
    the problem is, with GC disabled, everything is working properly, but with GC enabled, the icon just flashes once and is gone, as if GC already destroyed my object. By stepping through code, I found out this is actually happening, the moment awakeFromNib finishes, theItem will be freed!

    on a side note, I do retain theItem with GC disabled, but when GC is enabled, documentation says it's not needed, as it doesn't do anything.

    any help would be appreciated!
     
  2. Moderator emeritus

    kainjow

    Joined:
    Jun 15, 2000
    #2
    That is interesting. Just quickly glancing through Leopard's documentation on ADC I don't see anything about this.

    A possible hack to fix it would be to add it to an ivar array (?).

    Edit: just tested this and it works for me. Maybe your image doesn't exist? Try giving it a title instead of an image and see if it shows up then.
     
  3. macrumors member

    Joined:
    May 29, 2006
    Location:
    Melbourne, Australia
    #3
    W.a.g.

    Without knowing much about Objective-C 2.0 and its garbage collection (I've yet to start playing with this myself), but with experience with the (similar?) .NET garbage collector, the key thing is to make sure there is a (direct or indirect) pointer to your instance kept within the main part of the program while it is running.

    IIRC, the GC starts with your global variables and works through all of the pointers, marking everything it finds as being in use - everything else is assumed to be garbage.

    Therefore as long as there is a path through to your instance of the 'Controller' class, theItem should be followed by the GC and so keep your status bar item.

    Susan
     
  4. macrumors 68020

    Gelfin

    Joined:
    Sep 18, 2001
    Location:
    Denver, CO
    #4
    Perhaps this is relevant? It comes from the GC Programming Guide:

     
  5. macrumors 6502a

    Joined:
    Apr 11, 2007
    Location:
    Rocklin, CA
    #5
    Did you build your app with GC-supported or GC-required? You want GC-required in this case.
     
  6. macrumors newbie

    Joined:
    Dec 6, 2007
    #6
    Hi, I'm having the same problem but don't have much prior Cocoa experience. My code is trivially similar:

    Code:
    @interface ApplicationController : NSObject {
      NSStatusItem *statusItem;
    }
    
    @end
    
    @implementation ApplicationController
    - (void)awakeFromNib
    {
      statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
      [statusItem setTitle:@"Hello World"];
      sleep(1); // To see if it shows up
    }
    @end
    
    I've tried putting the statusItem in an NSMutableArray which is handled by the nib as well as connecting all sorts of outlets in interface builder to prevent the garbage collection. But no luck.

    If someone could describe the simplest possible solution, it would be much appreciated.
     
  7. Moderator emeritus

    kainjow

    Joined:
    Jun 15, 2000
    #7
    I've been trying to get an NSStatusItem working properly with a custom view using GC and Obj-C 2.0, and it works about 50% of the time. Basically every other time I select my menu, I get context errors and it crashes:

    I put in some checking for a bad CGContextRef, but it didn't help. Then one time I got a memory error (even though I'm compiling with GC!). So I turned off GC, and it works fine. I thought I'd try writing my new app with GC but I guess there are still bugs :rolleyes:

    Plus, GC and Cocoa code doesn't look right - inits without releases scare me ;)
     
  8. macrumors 6502

    Joined:
    Dec 6, 2006
    #8
    I haven't done anything with Obj2 yet, but using my physic debugging skills I can see your problems.

    You haven't created a copy of the status bar or bar obect, or what ever object you want to use. You are asking a convenience method to create an object for you. That is fine in a reference counted environment as the NSPool will free the object when the reference count returns to 0. But in the GC case there is no reference count, and since the runtime can't see any link between your program, nib and the system bar, it just cleans up the memory. There should be a tutorial on this on Apple's site.
     
  9. Moderator emeritus

    kainjow

    Joined:
    Jun 15, 2000
    #9
    Ok in response to the OP's problem... I just recreated my basic project again and it didn't work at first using awakeFromNib. Then I changed it to applicationDidFinishLaunching: and it works, and then changed it to init and it worked, and then changed it back to awakeFromNib: and it worked :confused:. Don't ask me how or why but all I know is GC is screwy :eek:

    EDIT: ok in between my changing of the methods, I set the NSApplication's delegate, and guess what? It fixes it:

    Code:
    - (void)awakeFromNib
    {
    	[NSApp setDelegate:self];
    	statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
    	[statusItem setHighlightMode:YES];
    	[statusItem setTitle:@"!!"];
    }
    If you comment out the first line, it doesn't work. Trust me I just tried that like 10 times in a row. Is this a bug or am I missing something with the framework? :confused:

    Edit 2: the reason the setDelegate: hack works is because it's holding onto self. Without that, GC is collecting self (I can see that finalize gets called) and so the statusItem gets collected as well. I'm not sure what the best way to fix this is. You can also use NSGarbageCollector's disableCollectorForPointer: but that doesn't seem right either.
     
  10. macrumors 6502

    Joined:
    Mar 31, 2005
    Location:
    London, England
    #10
    See here:

    Doesn't that explain this?
     
  11. Moderator emeritus

    kainjow

    Joined:
    Jun 15, 2000
    #11
    I guess I missed that :eek:. Still, it doesn't work even though my IBOutlet is connected properly.
     
  12. macrumors 6502

    Joined:
    Dec 6, 2006
    #12
    Your IBOutlet isn't holding onto a reference. It is a pointer which points to a reference.
     
  13. macrumors 6502

    Joined:
    Mar 31, 2005
    Location:
    London, England
    #13
    Which outlet? The one between your Controller and the menu item? That won't help. To keep your Controller object from being collected, you should connect an outlet from the File's Owner to it, and make sure that the File's Owner is also safe from collection.

    I still don't know why the OP was having a problem with his menu item being collected after adding it to the system status bar. That's a mystery to me.
     
  14. macrumors newbie

    Joined:
    Dec 6, 2007
    #14

    I used the setDelegate: trick and it works just like you said. Thank you for taking the time to answer this and for explaining why this works. Hopefully it will help others experiencing similar problems.

    Thanks again!
     
  15. macrumors regular

    Joined:
    Nov 7, 2004
    #15
    Thanks

    Thanks for putting up this thread. I've just ran into this issue when trying to port over some objective-c code. I hope some seasoned developers could go in depth on how to fix this without the NSApplication delegate fix.


    Anyway, she works... thanks!
     
  16. macrumors 68020

    Krevnik

    Joined:
    Sep 8, 2003
    #16
    Well, it looks like the answer is in the thread, just lost in the noise. :)

    NSApp is a root object, so by setting the delegate, you attached the controller to a root object, preventing it from being collected. It should work okay once you do two things:

    1) Set GC-Required (to avoid pesky NSAutoreleasePool things from potentially becoming a factor)...

    2) In the file owner of the nib, have an outlet to the controller (if the controller isn't already the file owner), and then attach the outlet to the controller. Your file owner class will have to actually /have/ the outlet in its implementation, or this won't work.
     

Share This Page