views:

456

answers:

1

Hi, i'm trying to get a Outline view to display a directory, now I've edited the example from Apple to make it work from any directory I set, except when expanding any node I get "EXEC_BAD_ACCESS" from the NSOutlineView class.

Here is the header file:

#import <Cocoa/Cocoa.h>

@interface SMLDirectoryDataSource : NSObject {
    NSString *rootDirectory;
}

- (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item;
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item;
- (id)outlineView:(NSOutlineView *)outlineView
      child:(int)index
        ofItem:(id)item;
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn
        byItem:(id)item;
- (void) setRootDirectory:(NSString *)directory;

@end

@interface SMLDirectoryDataItem : NSObject
{
    NSString *relativePath, *fullPath;
    SMLDirectoryDataItem *parent;
    NSMutableArray *children;
}

//+ (SMLDirectoryDataItem *)rootItem;
- (int)numberOfChildren;// Returns -1 for leaf nodes
- (SMLDirectoryDataItem *)childAtIndex:(int)n;// Invalid to call on leaf nodes
- (NSString *)fullPath;
- (NSString *)relativePath;

@end

And here is the implementation file:

#import "SMLDirectoryDataSource.h"


@implementation SMLDirectoryDataSource
- (id)initWithDirectory:(NSString *)path
{
    rootDirectory = path;
    return self;
}

- (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
{
    return (item == nil) ? 1 : [item numberOfChildren];
}

- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
    return (item == nil) ? NO : ([item numberOfChildren] != -1);
}

- (id)outlineView:(NSOutlineView *)outlineView
      child:(int)index
        ofItem:(id)item
{
    NSLog(@"hi there");
    if(rootDirectory == nil)
      rootDirectory = @"/";
    NSLog(rootDirectory);
    if(item == nil){
     SMLDirectoryDataItem *item = [[SMLDirectoryDataItem alloc] initWithPath:rootDirectory parent:NULL];
     return item;
     [item release];
    }
    else
     return [(SMLDirectoryDataItem *)item childAtIndex:index];
}
/*(
- (id)outlineView:(NSOutlineView *)outlineView
objectValueForTableColumn:(NSTableColumn *)tableColumn
        byItem:(id)item
{
    if(rootDirectory == nil)
     rootDirectory = @"/";
    return rootDirectory;
}
*/
- (void)setRootDirectory:(NSString *)directory
{
    rootDirectory = directory;
}

- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
    if(item == nil)
     return rootDirectory;
    else
     return (id)[(SMLDirectoryDataItem *)item relativePath];
}

- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item {
    return NO;
}

@end

@implementation SMLDirectoryDataItem

//static SMLDirectoryDataItem *rootItem = nil;
#define IsALeafNode ((id)-1)

- (id)initWithPath:(NSString *)path parent:(SMLDirectoryDataItem *)obj
{
    fullPath = [path copy];
    if (self = [super init])
    {
        relativePath = [[path lastPathComponent] copy];
        parent = obj;
    }
    return self;
}


/*+ (SMLDirectoryDataItem *)rootItem
{
    if (rootItem == nil) rootItem = [[SMLDirectoryDataItem alloc] initWithPath:@"/" parent:nil];
    return rootItem;
}*/


// Creates, caches, and returns the array of children
// Loads children incrementally
- (NSArray *)children
{
    if (children == NULL) {
        NSFileManager *fileManager = [NSFileManager defaultManager];
        //NSString *fullPath = [self fullPath];
        BOOL isDir, valid = [fileManager fileExistsAtPath:fullPath isDirectory:&isDir];
        if (valid && isDir) {
      NSArray *array = [fileManager contentsOfDirectoryAtPath:fullPath error:NULL];
      if (!array) {   // This is unexpected
       children = [[NSMutableArray alloc] init];
      } else {
       NSInteger cnt, numChildren = [array count];
       children = [[NSMutableArray alloc] initWithCapacity:numChildren];
       NSString *filename = [[NSString alloc] init];
       for (cnt = 0; cnt < numChildren; cnt++) {
        filename = [fullPath stringByAppendingPathComponent:[array objectAtIndex:cnt]];
        SMLDirectoryDataItem *item = [[SMLDirectoryDataItem alloc] initWithPath:filename parent:self];
        [children addObject:item];
        [item release];
       }
       [filename release];
      }
        } else {
      NSLog(@"is a leaf... strange");
            children = IsALeafNode;
        }
    }
    return children;
}


- (NSString *)relativePath
{
    return relativePath;
}


- (NSString *)fullPath
{
    // If no parent, return our own relative path
    //if (parent == nil) return relativePath;

    // recurse up the hierarchy, prepending each parent’s path
    //return [[parent fullPath] stringByAppendingPathComponent:relativePath];
    return fullPath;
}

- (SMLDirectoryDataItem *)childAtIndex:(int)n
{
    return [[self children] objectAtIndex:n];
}

- (int)numberOfChildren
{
    id tmp = [self children];
    return (tmp == IsALeafNode) ? (0) : [tmp count];
}


- (void)dealloc
{
    if (children != IsALeafNode) [children release];
    [relativePath release];
    [super dealloc];
}

@end

Update: updated the code with the latest version

+4  A: 

You aren't managing memory correctly.

(1) This line of code leaks. Autorelease the SMLDirectoryDataItem instance.

    return (item == nil) ? [[SMLDirectoryDataItem alloc] initWithPath:rootDirectory parent:nil] : [item childAtIndex:index];

(2) In your -initWithPath:parent: method, the following line of code does not retain the string. The autorelease pool releases it when drained. This is most likely leading to your crash:

    relativePath = [path lastPathComponent];

Review this:

http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/MemoryMgmt/MemoryMgmt.html

There are some additional problems in the code (the updated code):

(1) First and foremost, this...

#define IsALeafNode ((id)-1)

.... is completely wrong. You are passing -1 into things that expect objects. Immediate crash if anything retains/autoreleases or otherwise messages that.

(2) Also, you still aren't managing memory correctly. Your -setRootDirectory: method is not retaining the string. I would suggest using an @property and @synthesizing the setter/getter.

(3) Your -children method is leaking strings like a sieve. Specifically, the filename variable's usage is wrong.

bbum
okay so i changed it so it doesn't crash but now it says "[__NSCFType numberOfChildren]: unrecognized selector sent to instance"
kennyisaheadbanger
Then you're going to have to do some debugging. Break on -outlineView:numberOfChildrenOfItem: and see what the value and class of item is. Then determine how the class of item came to be what it is.
Rob Keniger
Now i'm really confused.. i've copied the updated example things in and now for the method objectValueForTableColumn is making it crash, even though it's called around 4 times beforehand and operating okay... the if(item == nil) isn't working for some reason!
kennyisaheadbanger
eurgh... i don't get why copying the example and changing it to work with a different folder could make so many problems
kennyisaheadbanger
I found the original code in the docs. You have made quite a few changes; I'd suggest re-comparing the two and then thinking through each line of the change. The IsALeafNode is in the original and I have filed a bug against that -- totally bogus
bbum
I've noticed that the OutlineView example has more up to date than the NSOutlineView conceptual documents example where I copied it from... i will update it and see what happens
kennyisaheadbanger
still don't know why this crash is happening... the debugger isn't much use it's saying item is something called `isa` and just goes down to the same thing.... i'm really really confused
kennyisaheadbanger
i've fixed it by using some other thing i found via google code search, but thank you very much for your help.. i'm going to tick it for you :D
kennyisaheadbanger