Search code examples
objective-cmemory-managementexc-bad-access

Removing an element from NSDictionary causes it to be deallocated prematurely


Full project can be seen here (for context: https://github.com/atlas-engineer/next-cocoa)

The following code returns EXC_BAD_ACCESS:

- (bool)windowClose:(NSString *)key
{
    NSWindow *window = [[self windows] objectForKey:key];
    [[self windows] removeObjectForKey:key];
    [window close];
    return YES;
}

The following code, however, works

- (bool)windowClose:(NSString *)key
{
    [[self windows] removeObjectForKey:key];
    return YES;
}

As does the following:

- (bool)windowClose:(NSString *)key
{
    NSWindow *window = [[self windows] objectForKey:key];
    [window close];
    return YES;
}

It is only somehow when you put them together that everything breaks.

For reference, I've provided the AutokeyDictionary implementation below, which is the value of [self windows] in the examples above

//
//  AutokeyDictionary.m
//  next-cocoa
//
//  Created by John Mercouris on 3/14/18.
//  Copyright © 2018 Next. All rights reserved.
//

#import "AutokeyDictionary.h"

@implementation AutokeyDictionary
@synthesize elementCount;

- (instancetype) init
{
    self = [super init];
    if (self)
    {
        [self setElementCount:0];
        _dict = [[NSMutableDictionary alloc] init];
    }
    return self;
}

- (NSString *) insertElement:(NSObject *) object
{
    NSString *elementKey = [@([self elementCount]) stringValue];
    [_dict setValue:object forKey: elementKey];
    [self setElementCount:[self elementCount] + 1];
    return elementKey;
}

- (NSUInteger)count {
    return [_dict count];
}

- (id)objectForKey:(id)aKey {
    return [_dict objectForKey:aKey];
}

- (void)removeObjectForKey:(id)aKey {
    return [_dict removeObjectForKey:aKey];
}

- (NSEnumerator *)keyEnumerator {
    return [_dict keyEnumerator];
}

- (NSArray*)allKeys {
    return [_dict allKeys];
}

@end

Lastly, for the record, turning on zombies does make the code work, though that is obviously not a solution.


Solution

  • Your window's releasedWhenClosed property is probably defaulting to YES, which is likely to conflict with ARC's memory management. Set it to NO when you create the window.