ollien
ollien

Reputation: 4766

viewDidLoad deallocated prematurely in ARC?

I'm writing an app which has a NSOutlineView, and an interface which implements NSOutlineViewDataSource. However, at runtime, the app crashes with an error that a message was sent to a deallocated instance. I did some debugging with zombies and and Instruments and I found the address that was deallocated.

Instruments

I can't quite figure out what's going on here. I've determined that if I disable ARC for ViewController.m it doesn't segfault. Here's the relevant code.

-(void)viewDidLoad {
    [super viewDidLoad];
    NSArray * array = [[NSArray alloc]initWithObjects:@"chrisdotcode",@"Mop",@"ollien", nil];
    [self.namesList setDataSource:[[ArrayOutlineDataSource alloc] init:array]];
}

And here's the ArrayOutlineDataSource, which implements NSOutlineViewDataSource.

@implementation ArrayOutlineDataSource
    -(id)init:(NSArray *)array{
        self.array = array;
        return [super init];
    }
    -(id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item{
        return self.array[index];
    }
    -(BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item{
        return false;
    }
    -(NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item{
        NSLog(@"Item:%@",item);
        NSLog(@"Item count:%lu",(unsigned long)[self.array count]);
        return [self.array count];
    }
    -(id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item{
        return self.array;
    }



@end

Upvotes: 2

Views: 44

Answers (1)

Rob
Rob

Reputation: 437552

The documentation says:

An outline view does not own its data source. Similarly, it does not own the objects it gets from the data source—if they are released your application is likely to crash unless you tell the outline view to reload its data.

The data source is a controller object, and you are responsible for ensuring that it is not deallocated before the outline view is finished with it (typically the data source is an object such as the document object in a document-based application, so there is no additional work to do). The data source is in turn responsible for retaining all of the objects it provides to an outline view, and updating the outline view when there’s a change to the model. It is therefore not safe to release the root item—or any children—until you’re no longer displaying it in the outline view.

Bottom line, the outline view does not maintain a strong reference to its data source. (This is very common: Usually data sources and delegates are weak references.) The code sample in the question is not retaining the ArrayOutlineDataSource.

Create your own property to maintain a strong reference to this ArrayOutlineDataSource:

@property (nonatomic, strong) ArrayOutlineDataSource *dataSource;

And then:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSArray * array = [[NSArray alloc]initWithObjects:@"chrisdotcode",@"Mop",@"ollien", nil];
    self.dataSource = [[ArrayOutlineDataSource alloc] init:array];
    [self.namesList setDataSource:self.dataSource];
}

Upvotes: 1

Related Questions