I'm making a non-garbage-collected MacFUSE Cocoa application, inside of which I want to use a GCD block as a delegate. However, my program crashes during the invocation of the block, leaving only an EXC_BAD_ACCESS in its trail.
My program uses a framework built agains the Mac OS 10.5 SDK that does not support garbage collection (nor 64 bits) and the MacFUSE framework. The program builds with no warning or error as a 32-bit program. Other build settings (such as optimization level) were left to their original values.
So I have my application controller, from which I create this block and call runWithContinuation:
AFSPasswordPrompt* prompt = [[AFSPasswordPrompt alloc] initWithIcon:icon];
dispatch_block_t continuation = ^{
archive.password = prompt.password;
[self mountFilesystem:fsController];
[prompt performSelectorOnMainThread:#selector(release) withObject:nil waitUntilDone:NO];
};
[prompt runWithContinuation:continuation];
runWithContinuation: retains the block and instantiates a nib. The block is called only once the user dismisses the password prompt by pressing the "Open" button.
-(void)runWithContinuation:(dispatch_block_t)block
{
continuation = [block retain];
[passwordPrompt instantiateNibWithOwner:self topLevelObjects:NULL];
imageView.image = image;
[window makeKeyWindow];
}
-(IBAction)open:(id)sender
{
continuation();
[self close];
}
-(void)close
{
[window close];
[continuation release];
}
My problem is that when I hit continuation(), my program triggers an EXC_BAD_ACCESS, and the last stack frame is called ??. Right under it is the open: method call.
I really don't know where it's coming from. NSZombies are enabled, and they don't report anything.
Any ideas?
try copying the block instead of retaining it. A block lives on the stack until you call copy, then it is copied to the heap.
Related
I have the following method in my GameWindowController (subclass of NSWindowController):
- (void)windowWillClose:(NSNotification *)notification {
AppDelegate *delegate = [NSApp delegate];
[delegate removeGameWindowController:self];
}
The code for removeGameWindowController in AppDelegate is:
- (void)removeGameWindowController:(GameWindowController*)controller {
[self.controllers removeObject:controller];
}
self.controllers is an NSMutableArray with all my GameWindowControllers.
The above code seems to have a race condition. It will randomly crash with EXC_BAD_ACCESS when I close windows, almost every time if I close all windows at once.
My guess is that ARC is deallocating the window controller before or as removeGameWindowController: returns, leaving the window with a dangling pointer to the controller. I have tried adding controller.window.windowController = nil; to no avail.
For some reason, using the (BOOL)windowShouldClose:(id)sender delegate method instead as suggested in https://stackoverflow.com/a/11782844/344544 works, but is not an acceptable solution as it is not called upon quit.
How can I reliably remove my window controllers from the array of controllers after each window has closed? Is there some other delegate method which gets called or some NSNotification I can subscribe to which fire after a window has finished closing?
After lengthy investigation and step by step running in the debugger I figured out the source of the problem and a possible solution.
The window controller was indeed being released at some point after the end of removeGameWindowController: along with all its strong references which include the NSWindow. If the window was released before the stack had unwound back to the close call on the window itself, the program would crash while finishing the that function as self in this particular case is a dangling pointer.
I was unable to find a way to get notified after a window had closed, however it is likely such an approach would have had the exact same problem.
In order to ensure no reference to the window was left anywhere on the stack I queued the removal of the window controller from the array to happen as a subsequent event on the runloop:
- (void)removeGameWindowController:(GameWindowController*)controller {
[self.controllers performSelectorOnMainThread:#selector(removeObject:) withObject:controller waitUntilDone:NO];
}
Currently I didn't use ARC in my app, and I tried to create a NSAlert object as a local variable, at the end of function, I didn't release it.
I expected that the app crash with that, the function code is here.
#define GLOBAL_VARIABLE 0
NSString *msgText = [self.defaultValueTextFiled stringValue];
NSString *informativeText = #"Informative Text";
#if !GLOBAL_VARIABLE
NSAlert *alertView = nil;
#endif
if (alertView == nil)
{
alertView = [[NSAlert alloc] init];
[alertView setMessageText:msgText];
[alertView setInformativeText:informativeText];
NSTextField *accessory = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,200,22)];
[accessory setStringValue:#"accessory result"];
[alertView setAccessoryView:accessory];
}
NSView *accessory= nil;
NSInteger result = [alertView runModal];
if (result == NSAlertAlternateReturn)
{
accessory = [alertView accessoryView];
if (accessory != nil)
{
NSTextField *txtFiled = (NSTextField *)accessory;
NSLog(#"%ld", [accessory retainCount]);
NSString *str = [txtFiled stringValue];
NSLog(#"%ld", [accessory retainCount]);
[self.resultValueTextField setStringValue:str];
NSLog(#"%ld", [accessory retainCount]);
}
}
Questions:
(1) There is no [alertView release] in the end, why not crash? It has even no leaks.
(2) Refer to here , the accessory view shouldn't be release. However, I tried to release the view before [alertView runModal], then get its stringValue later, that could works. Why?
(3) The return value of function retainCount is interesting. When I created the alertView object, the retainedCount of alertView is 3. (Why?)
Before [alertView setAccessoryView:accessory], the accessory's retainedCount is 1, then it changed to 2 after executing it. That's normal and right. However, the log result for the above code, they're 20, 21, 22. How did they come from?
Thanks for your attention!
It does leak. Put a breakpoint on dealloc (or create a subclass of NSAlert and add a log statement to dealloc) to show this
The accessry view should be released. You alloc it and then it would be retained by the alertView
Here's a concise guide on when to use retainCount.
The rules for memory management are generally quite simple. Most problems come from overthinking them and trying to guess what other parts of the system are going to do, rather than just following the rules as written.
The first rule of Memory Management:
Use ARC.
If you cannot follow rule one (for instance, you are developing for OS X 10.5 as I do), then here are the rules:
You must balance each call you make to +alloc…, +new…, -…copy… or -retain with a call to -release or -autorelease.
That's really it. Everything else is really just commentary to help you follow that rule. So, you called [NSAlert alloc] and [NSTextField alloc], you need to call release on those objects.
Why doesn't it crash?
Because a leak is not going to crash unless it causes you to run out of memory.
Why doesn't it leak?
The most likely cause is that you're only running it once and then expecting Instruments to detect the leak. It may not show up if you only run it once. It depends on how NSAlert is internally implemented. Instruments finds leaks by walking all the pointers in the system and determining if any accessible pointers still reference a piece of allocated memory. There are many cases when a "leak is not a leak."
But this also suggests you're not running the static analyzer, because the analyzer should definitely have detected that leak. Run the static analyzer (Cmd-Shift-B) all the time and clean up what it finds.
the accessory view shouldn't be release.
That's not what the link you reference says. You shouldn't add an extra release. But in the referenced code, you'll notice that they release the view to balance their own +alloc.
… retainCount …
Never use -retainCount. Not even for debugging. Not even for anything else. There is no point at which retainCount is going to return you a piece of information that is going to be more enlightening than confusing. Why is it greater than you think it should be? Because other objects are retaining the alert view? Which objects? That's not your business. That's an internal implementation detail, subject to change. It could be pending autorelease calls, which show up temporarily as a "too high retainCount". It could be the run loop. It could be an internal controller. It could be anything. Calling retainCount tells you nothing useful.
Now that you understand manual memory management, switch to ARC and think about object graphs rather than retain counts. It is a much better way to deal with memory.
I have a Cocoa app with ARC enabled. I am using OS 10.8 and Xcode 4.4. The app sporadically crashes upon calls to CGLayerRelease, dumping messages like the following to console:
error for object 0x10a2d2000: entry for pointer being freed from death-row vanished
If I comment out the calls to CGLayerRelease the crashing stops. Do I need to call CGLayerRelease in ARC enabled projects? The documentation isn't very clear about this.
It is hard to post code for this because it is spread throughout a large file for a class that inherits from NSView. The class has a data member CGLayerRef layer;. Then inside the drawRect function:
if (layer == nil) {
layer = CGLayerCreateWithContext(context, self.frame.size, NULL);
// draw some stuff into CGLayerGetContext(layer)
}
CGContextDrawLayerInRect(context, self.bounds, layer);
The class is also a delegate of its window and has the following function
- (void)windowDidEndLiveResize:(NSNotification *)notification {
if (layer) {
//CGLayerRelease(layer);
layer = nil;
}
[self setNeedsDisplay:YES];
}
Also inside a property setter
- (void)setPitch:(NSArray *)aPitch {
pitch = aPitch;
if (layer) {
//CGLayerRelease(layer);
layer = nil;
}
}
Now if I uncomment the calls to CGLayerRelease then I sporadically get the crash mentioned above. I stress "sporadically" because it does not always happen.
Yes, you need to call CGLayerRelease() in ARC. The above assignments look correct, but I suspect you may have others. You're directly accessing your ivars, which is the #1 cause of these kinds of crashes. Do not access your own ivars except in init and dealloc. Create an accessor for setLayer: that correctly releases the old one and creates a new one and always use that.
It's very possible that your CGLayer code is fine, and that it's something else like a CGPath that you're adding to the CGLayer. Move all your ivars to accessors. Otherwise you will be chasing these kinds of bugs forever.
I've got a MFMailComposeViewController object that I'm releasing in my Email button's callback, simply because I created it, and I think my doing so is intermittently but not always crashing my app.
How can I use Xcode's instrumentation program to detect such a situation?
Thanks.
You can set the NSZombieEnabled environment variable to YES (Product > Edit Scheme…, the select Run (Product Name), click the Arguments tab and edit the list of environment variables). With NSZombie, objects will not be deallocated but turned into zombies. Messaging them will log an error to the console instead of crashing with EXC_BAD_ACCESS. That way you can find out if it’s really the MFMailComposeViewController that’s causing trouble.
But retaining and releasing the view controller might not even be necessary. If you present the MFMailComposeViewController immediately after creating it and don’t use it anymore after it’s been dismissed, there’s no need to retain it:
- (IBAction)composeMessage:(id)sender {
MFMailComposeViewController *mailComposeViewController = [[[MFMailComposeViewController alloc] init] autorelease];
mailComposeViewController.mailComposeDelegate = self;
[self presentModalViewController:mailComposeViewController animated:YES];
}
- (void)mailComposeController:(MFMailComposeViewController *)mailComposeViewController didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error {
// Present error to the user if failed
[self dismissModalViewControllerAnimated:YES];
}
I am new to NSOpenPanel/NSSavePanel/NSPanel. I am using NSOpenPanel to choose a directory whose files my app will iterate over and do some fairly time-consuming processing.
I can call -close on the panel, but that does not return focus to the main window. I have read a lot about "dismissing" the panel - but I haven't found any methods that "dismiss" rather than "close" a panel or a window.
Is it just that I need to spawn a background thread (NSOperation)?
This is what my -chooseDidEnd:returnCode:contextInfo:
-(void) chooseDidEnd:(NSOpenPanel *)panel returnCode:(int)returnCode contextInfo:(void *)contextInfo
{
[panel orderOut:self];
[panel release];
if (returnCode == NSFileHandlingPanelOKButton)
{
[progressIndicator startAnimation:self];
[self doLotsOfTimeConsumingWork:[[panel URL] path]];
[progressIndicator stopAnimation:self];
}
}
While the NSOpenPanel does go away, my NSProgressIndicator does not animate and the main window doesn't come alive until after -doLotsOfTimeConsumingWork: completes.
Update
Just looked at NSOperationSample code, and it is looking like that's the way to go.
Two notes:
First, in Cocoa, the event handling and drawing happens on the main thread. Hence it is never a good idea to synchronously call lengthy methods there (which is the reason for your unresponsive UI).
So yes, you should hand off computationally expensive tasks to a secondary thread from this method, like from any IBAction.
Second, calling [panel release] in that method violates Cocoa's rules of object ownership! So if you would be leaking the panel without that call, you should fix that in the method where you're creating the panel.