I'm writing a sandboxed ARC app with a view-based NSTableView that accepts dragged-and-dropped files (NSURL
s). I ran into some significant strangeness in the following NSTableViewDelegate
method:
- (NSView *)tableView:(NSTableView *)tv
viewForTableColumn:(NSTableColumn *)tc
row:(NSInteger)row
{
// `files' is an NSMutableArray* ivar containing NSURLs
// that have been dropped into this table
NSURL *url = [files objectAtIndex:row];
NSString *fileName = [url lastPathComponent];
NSImage *icon = [self iconForURL:url];
NSTableCellView *view = [tv makeViewWithIdentifier:[tc identifier] owner:self];
[[view textField] setStringValue:fileName];
[[view imageView] setImage:icon];
return view;
}
I can drag one file into the table view, and it displays correctly. When I drag a second file, I get this error:
*** Canceling drag because exception 'NSRangeException' (reason '*** -[__NSArrayM insertObject:atIndex:]: index 1 beyond bounds for empty array') was raised during a dragging session
Stepping through the debugger, I discovered that files
"becomes empty" -- actually becomes a new object instance -- after the call to makeViewWithIdentifier:owner:
. I assume this is some aspect of ARC that I don't understand, but it seems to me that the object has a strong reference to its own ivar (by default); how could it get released and re-created out from under me?
I have come up with two hacks to work around this:
- pass the ivar as the owner of the table cell view (hoping that in future releases it will continue to hold a strong reference); or
- create a local variable to point to the ivar's object and re-assign the ivar to the old object (this is obviously wasteful as it creates a replacement array in the meantime).
What am I missing here? These workarounds should not be necessary.
Calling -makeViewWithIdentifier:owner: will cause an -awakeFromNib message to be sent to owner. This is documented but only in the header file (EDIT: the main documentation has been updated to refer to this).
I imagine that your files array is simply becoming reinitialised in -awakeFromNib.
The solution in the given case (which is loading a view prototype rather than a nib) is simply to pass nil as the owner. Other implementations which load registered nibs (see -registerNib:forIdentifier:) may likely require an owner, which may be the delegate (or not). So multiple calls to
-awakeFromNib
may have to be detected and trapped. It is trivial to set a property to flag nib loading and only perform required initialisation once.Note that the Apple docs for this method have been updated to reflect this: