PDA

View Full Version : Problem with objc2 garbage collection!




shayanoh
Nov 1, 2007, 11:19 AM
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.

@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!



kainjow
Nov 1, 2007, 03:14 PM
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.

AussieSusan
Nov 1, 2007, 05:04 PM
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

Gelfin
Nov 1, 2007, 09:01 PM
Perhaps this is relevant? It comes from the GC Programming Guide:


Nib files

Since the collector follows strong references from root objects, and treats as garbage all objects that cannot be reached from a root object, you must ensure that there are strong references to all top-level objects in a nib file (including for example, stand-alone controllers)—otherwise they will be collected. You can create a strong reference simply by adding an outlet to the File's Owner and connecting it to a top-level object. (In practice this is rarely likely to be an issue.)

Alloye
Nov 2, 2007, 12:09 PM
Did you build your app with GC-supported or GC-required? You want GC-required in this case.

badcarl
Dec 11, 2007, 01:56 PM
Hi, I'm having the same problem but don't have much prior Cocoa experience. My code is trivially similar:


@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.

kainjow
Jan 10, 2008, 03:39 AM
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:

<Error>: CGContextResetClip: invalid context
<Error>: CGContextReplaceTopGState: invalid context
<Error>: CGContextClipToRect: invalid context
<Error>: CGContextTranslateCTM: invalid context
<Error>: CGContextClipToRect: invalid context
<Error>: CGContextClipToRect: invalid context
<Error>: CGContextClipToRects: invalid context
<Error>: CGContextSetFillColorWithColor: invalid context
<Error>: CGContextSetStrokeColorWithColor: invalid context
<Error>: CGContextFillRects: invalid context

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 ;)

garethlewis2
Jan 10, 2008, 06:20 AM
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.

kainjow
Jan 10, 2008, 06:33 AM
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:

- (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.

Nutter
Jan 10, 2008, 06:55 AM
See here (http://developer.apple.com/documentation/Cocoa/Conceptual/GarbageCollection/Articles/gcEssentials.html#//apple_ref/doc/uid/TP40002452):

Since the collector follows strong references from root objects, and treats as garbage all objects that cannot be reached from a root object, you must ensure that there are strong references to all top-level objects in a nib file (including for example, stand-alone controllers)—otherwise they will be collected. You can create a strong reference simply by adding an outlet to the File's Owner and connecting it to a top-level object. (In practice this is rarely likely to be an issue.)

Doesn't that explain this?

kainjow
Jan 10, 2008, 07:04 AM
See here (http://developer.apple.com/documentation/Cocoa/Conceptual/GarbageCollection/Articles/gcEssentials.html#//apple_ref/doc/uid/TP40002452):



Doesn't that explain this?

I guess I missed that :o. Still, it doesn't work even though my IBOutlet is connected properly.

garethlewis2
Jan 10, 2008, 08:40 AM
Your IBOutlet isn't holding onto a reference. It is a pointer which points to a reference.

Nutter
Jan 10, 2008, 08:45 AM
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.

badcarl
Jan 10, 2008, 04:59 PM
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:

- (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.


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!

rev316
Jan 22, 2008, 02:09 PM
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!

Krevnik
Jan 22, 2008, 05:53 PM
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.


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.