EXC_BAD_ACCESS when I close my window, which is also my application's delegate
Asked Answered
C

3

0

I wrote a Cocoa Application and I got EXC_BAD_ACCESS error when I'm closing an application window. I read that this error usually means problems with memory, but I have ARC mode on and I don't need care about releasing e.t.c. (xCode forbids me to call this functions and manage memory automatically).

Error is pointing at line return NSApplicationMain(argc, (const char **)argv); in main function.

Here's my application's code:

.h file:

@interface MainDreamer : NSWindow <NSWindowDelegate> 
{    
    NSTextField *dreamField;
    NSTableView *dreamTable;    
    NSImageView *dreamview;

    NSMutableArray *dreamlist;  
    NSMutableArray *dataset;
}

@property (nonatomic, retain) IBOutlet NSTextField *dreamField;
@property (nonatomic, retain) IBOutlet NSTableView *dreamTable;
@property (nonatomic, retain) IBOutlet NSImageView *dreamview;
@property (nonatomic, retain) IBOutlet NSMutableArray *dreamlist;
@property (nonatomic, retain) IBOutlet NSMutableArray *dataset;
@property (assign) IBOutlet NSWindow *window;

@end

.m file:

@implementation MainDreamer

@synthesize window;
@synthesize dataset;
@synthesize dreamField;
@synthesize dreamlist;
@synthesize dreamview;
@synthesize dreamTable;

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification{
    NSString *applicationPath = [[NSBundle mainBundle] bundlePath];
    NSString *filename = [applicationPath stringByAppendingPathComponent:@"dreams"];
    NSLog(self.description);

    dreamlist = [[NSMutableArray alloc] init];  
    dataset = [[NSMutableArray alloc] init];
    dataset = [NSKeyedUnarchiver unarchiveObjectWithFile:filename];
    if([dataset count] != 0) {
        int i = 0;
        while (i < [dataset count]) { 
            Dream *dr = [[Dream alloc] init];
            dr = [dataset objectAtIndex:i];
            [dreamlist addObject: dr.dreamname];         
            i++;
        }
    }    
    [dreamTable reloadData]; 
}

-(void)applicationWillTerminate:(NSNotification *)notification{       
    NSString *applicationPath = [[NSBundle mainBundle] bundlePath];
    NSString *filename = [applicationPath stringByAppendingPathComponent:@"dreams"];
    [NSKeyedArchiver archiveRootObject:dataset toFile:filename];
    NSLog(@"finish");
}

- (void) mouseUp:(NSEvent *)theEvent{
    long index = [dreamTable selectedRow];
    Dream *dr = [[Dream alloc] init];
    dr = [dataset objectAtIndex:index];
    dr.dreampicture = dreamview.image;
    [dataset replaceObjectAtIndex:index withObject:dr];
    NSLog(self.description);
}

- (void) tableViewSelectionDidChange: (NSNotification *) notification{
    long row = [dreamTable selectedRow];
    Dream *dr = [[Dream alloc] init];
    dr = [dataset objectAtIndex: row];
    if(dr.dreampicture != NULL) 
        dreamview.image = dr.dreampicture;
    NSLog(@"selected row changed");
}

Class "Dream":

@interface Dream : NSObject <NSCoding>
{
    NSString *dreamname;
    NSImage *dreampicture;
}

@property (retain) NSString* dreamname;
@property (retain) NSImage* dreampicture;

-(id)initWithCoder:(NSCoder *)aDecoder;
-(void)encodeWithCoder:(NSCoder *)aCoder;

@end

What is wrong, why EXC_BAD_ACCESS occurs?I remind that I have xCode 4 with Automatic Reference Counting (ARC)

Thanks

UPDATE

I used Profile to find zombie event. So I found out this: An Objective-C message was sent to a deallocated object(zombie( at adress 0x108d85230)

Responsible Caller - [NSApplication(NSWindowCache) _checkForTerminateAfterLastWindowClosed: saveWindows:]

I had this function in code:

- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender{
    return TRUE;
}

However after I putted it in comments, this zombie event continue to occur.

Clance answered 4/12, 2011 at 8:52 Comment(7)
ARC doesn't prevent you from creating weak references, which could then be accessed after release if they are not nullified. You've declared your properties using pre-ARC specifiers (retain ad opposed to strong etc). The standard advice applies - enable zombies and see which object is receiving the message.Telson
@jrturton, how can I enable zombies?Clance
Go to menu Product -> Profile. Xcode will build your product an run it in Instruments. After a short time, the Instruments window pops up. Choose the "Zombies" template and click "Profile".Barbabra
It helped me to find out the zombie event. I updated my initial postClance
I think the problem could be the line @property (assign) IBOutlet NSWindow *window;. Could you explain what this is needed for? Do you use interface builder to link this window to itself? You can just self in that case...Barbabra
Another thought: What kind of object is MainDreamer? As it implements applicationDidFinishLaunching it seems it should be the app delegate, but it is declared as a subclass of NSWindow.Barbabra
I deleted IBOutlet NSWindow *window; - it was no necessity in it. As concerns MainDreamer class - it should be a subclass of NSWindow to have mouse events working.It is wired via delegate with File'S Owner witch class is NSApplication. In statistics' object summary there's information that _checkForTerminateAfterLastWindowClosed invokes after NSWindow close and NSWindow releaseClance
E
7

The crash is caused by the fact that you made the window your application's delegate. When you close the window, that is the last release that kills it off, and if it's the last window you had up, it causes the application to ask its delegate whether it should quit. Since the window you just killed off is the application's delegate, you get that crash.

Longer explanation and suggestion of solution in my answer on your subsequent question.

Economic answered 4/12, 2011 at 21:11 Comment(0)
B
3

This is wrong:

dataset = [[NSMutableArray alloc] init]; // WRONG
dataset = [NSKeyedUnarchiver unarchiveObjectWithFile:filename];

Why? You first allocate an empty array, and store that in the instance variable dataset. But in the next line, you replace the empty array with whatever +unarchiveObjectWithFile: returns. Why is this a problem? Well, if you read the docs, you'll see that it returns nil if the file is not found. This means that you now replace the empty array with nil, and all messages you send to dataset will be ignored (messages to nil are silently ignored in Objective-C)

I assume you actually want load the dataset from file, and only if that failed, start with an empty dataset:

dataset = [NSKeyedUnarchiver unarchiveObjectWithFile:filename];
if (dataset==nil) dataset = [[NSMutableArray alloc] init];

You have a similar error later on:

Dream *dr = [[Dream alloc] init]; // WRONG
dr = [dataset objectAtIndex:index];

You create a Dream object, and then replace it immediately with something from the dataset. What you actually want to do is:

Dream *dr;
dr = [dataset objectAtIndex:index];

or shorter:

Dream *dr = [dataset objectAtIndex:index];

Then again, you could replace the while loop with a fast-enumeration-style for loop:

    for (Dream *dr in dataset) {
        [dreamlist addObject: dr.dreamname];         
    }

Finally, to get to a point, I don't think the EXC_BAD_ACCESS actually occurs in main.h. I assume you use Xcode 4. Please use the thread/stack navigator in the right sidebar when debugging to find the actual position where the error occurs.

It could be that the error actually occurs in applicationWillTerminate:, because you try to archive dataset, which is probably nil, and it's probably not allowed to archive nil.

Barbabra answered 4/12, 2011 at 9:55 Comment(3)
Thanks for detailed answer. I corrected everything you said and checked that dataset is not nil. However EXC_BAD_ACCESS continue to occur. In Debug navigator there's Thread 1 where error fires(in 14 main ). I added Exception breakpoint but it wasn't activated. How can I find the actual position of error appearence?Clance
On the bottom of the sidebar, there's a slider where you can select which stack frames are shown. Drag it all the way to the right. Then you'll see all stack frames. And you can look where the problem occurs. It might be that the problem occurs somewhere within Apple's frameworks (it's quite common that memory management issues don't immediately cause errors...)Barbabra
I used profile and found out a zombie event, its caller is checkForTerminateAfterLastWindowClosed. I updated my post, you can rad more there.Clance
E
1

With ARC you should use strong and weak instead of retain and assign.

Exhale answered 4/12, 2011 at 9:9 Comment(2)
Especially the latter: weak is a zeroing weak reference, which will get set back to nil automatically when the referenced object dies. assign is synonymous with unsafe_unretained, which is a *non-*zeroing weak reference—even after the object dies, an assign/unsafe_unretained property will continue pointing to it. Definitely replace every use of assign with either weak or unsafe_unretained, and the latter only if you have a really, really good reason.Economic
I should add, however, that instances of some classes can't be referenced weakly. NSWindow is one of these. You must use unsafe_unretained (or assign, or strong/retain) on a property that refers to a window. See the FAQ in the Transitioning to ARC Release Notes: developer.apple.com/library/mac/releasenotes/ObjectiveC/…Economic

© 2022 - 2024 — McMap. All rights reserved.