PDA

View Full Version : Array losing its contents after a load (XCode/Objective C)




AJClayton
Mar 4, 2008, 10:39 AM
Hi

I'm new to Cocoa programming so apologise in advance that I'm likely to be missing something obvious. However, after several hours trying to fathom out a bug I'm completely stuck and would really appreciate some help!

I'm using XCode/Interface Builder 3 and Objective C 2.0 with garbage collection enabled. I have a Document-based application. In MyDocument.nib I have an NSTableView. This has its dataSource set to MyDocument in Interface Builder. I've implemented

- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
and
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex

In my application, I don't want users to be able to edit the table view directly. I've not used an NSArrayController. The user clicks a button, a sheet slides down from where various options are set including the text to be displayed in the table view. This all works fine and the table view happily displays the text I want, including amendments carried out via my sheet. So far, so good.

The next thing for me to do, I thought to myself, was to implement saving. I have Aaron Hillegass' Cocoa book so implemented

- (BOOL)loadDataRepresentation:(NSData *)data ofType:(NSString *)aType
and
- (NSData *)dataRepresentationOfType:(NSString *)aType

rather than the alternatives offered by XCode 3 as I can't see how to use these (I've got Aaron's next edition on pre-order). However, I don't think that this is at the route of my problem.

Clicking Save in my application successfully creates a saved file and if I open this in TextEdit I can see the values I've entered (amongst some binary data, of course). When I try to Open a saved file the load appears to work; a fact I've tested as follows:

- (BOOL)loadDataRepresentation:(NSData *)data ofType:(NSString *)aType {
NSLog(@"Loading data of type %@", aType);
newArray = [NSKeyedUnarchiver unarchiveObjectWithData:data];

if (newArray==nil) {
return NO;
} else {
NSLog(@"Loaded data");
[self setListOfGreetings:newArray];

// Debugging - let's see if my array has actually got anything in it
NSEnumerator *e = [listOfGreetings objectEnumerator];
greetingLister *import; // greetingLister is my model class
while (import = [e nextObject]) {
NSLog(@"%@", [import greeting]); // greeting is an NSString in my model class
}

NSLog(@"Size of loaded array %d", [newArray count]);
NSLog(@"Size of listOfGreetings %d", [listOfGreetings count]);

// greetingList is the tableview but sending reload/display messages doesn't work :-(
[greetingList reloadData];
[greetingList setNeedsDisplay:YES];

return YES;
}
}

For info, my numberOfRowsInTableView method shown in the output is:

- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView {
NSLog(@"tableView count %d", [listOfGreetings count]);
return [listOfGreetings count];
}

The output when I run my program and open a data file is:

[Session started at 2008-03-04 15:49:14 +0000.]
2008-03-04 15:49:14.865 TestApp[2457:10b] tableView count 0
2008-03-04 15:49:14.881 TestApp[2457:10b] tableView count 0
TestApp(2457,0xb0103000) malloc: free_garbage: garbage ptr = 0x105d760, has non-zero refcount = 1
TestApp(2457,0xb0103000) malloc: free_garbage: garbage ptr = 0x105cbd0, has non-zero refcount = 1
TestApp(2457,0xb0103000) malloc: free_garbage: garbage ptr = 0x106e4a0, has non-zero refcount = 1
TestApp(2457,0xb0103000) malloc: free_garbage: garbage ptr = 0x106edc0, has non-zero refcount = 1
2008-03-04 15:49:19.469 TestApp[2457:10b] Loading data of type DocumentType
2008-03-04 15:49:19.470 TestApp[2457:10b] Loaded data
2008-03-04 15:49:19.471 TestApp[2457:10b] This is some data from the saved file
2008-03-04 15:49:19.471 TestApp[2457:10b] Another entry from the saved file
2008-03-04 15:49:19.472 TestApp[2457:10b] Size of array 2
2008-03-04 15:49:19.472 TestApp[2457:10b] Size of listOfGreetings 2
2008-03-04 15:49:19.500 TestApp[2457:10b] tableView count 0
2008-03-04 15:49:19.510 TestApp[2457:10b] tableView count 0
TestApp(2457,0xb0103000) malloc: free_garbage: garbage ptr = 0x1068330, has non-zero refcount = 1

However (and here's the rub) the table view doesn't update automatically. What appears to be happening is that the array (listOfGreetings) contains objects in the loadDataRepresentation method but as soon as this method ends, the array has no length at all. It's totally baffled me!?

I've tried turning off garbage collection and sending a retain message to newArray in loadDataRepresentation with no luck, but that was at the point that my clutching at straws reached frantic levels.

For info, listOfGreetings is an NSMutableArray declared as follows:

In MyDocument.h:
@property(copy) NSMutableArray *listOfGreetings;

In MyDocument.m:
@synthesize listOfGreetings;

This array contains objects that are my model class. The model class is set up using Objective C 2.0's properties. I don't think it's relevant as I'm writing and reading the data successfully, but I can post this code if need be.

I realise that I've kept great chunks of my code out of this post, but it's still pretty long as it is! If any more info is required I'll be happy to post it.

Thanks, in advance, for any tips!



iSee
Mar 4, 2008, 12:38 PM
Here's an idea:

Since everytjhing seems fine at the end of loadDataRepresentation, but you don't see the data later, I wonder if there are two different instances of your object: loadDataRepresentation is called on one but the other is used thereafter. You could check for this by logging the pointer value of self in both loadDataRepresentation and numberOfRowsInTableView. If they are different then I'm on to something...

Eraserhead
Mar 4, 2008, 01:36 PM
Make sure you are retaining the array if you don't create it with an alloc init block you'll need to send it a retain message or it will be automatically cleared.

In that case you also need to make sure to release it in the dealloc block.

EDIT: Apologies if this post is a little technical.

kainjow
Mar 4, 2008, 04:12 PM
Make sure you are retaining the array if you don't create it with an alloc init block you'll need to send it a retain message or it will be automatically cleared.

In that case you also need to make sure to release it in the dealloc block.

He is using garbage collection though.


Post your code for the setListOfGreetings: method, or wherever you're creating listOfGreetings. I think the problem may lie in there.

AJClayton
Mar 4, 2008, 04:50 PM
Thanks very much for the helpful replies. I'll try to answer the questions asked above.

First of all, as suggested, I entered a simple line in loadDataRepresentation: and numberOfRowsInTableView to give the pointer to my object (using NSLog(@"%@", self); This gave the following output:

This is the log entry generated from loadData:
2008-03-04 22:35:20.577 TestApp[267:10b] <MyDocument: 0x1071990>
and this is the line generated from numberOfRows...:
2008-03-04 22:35:20.605 TestApp[267:10b] <MyDocument: 0x10728d0>

Looks like there's two MyDocument objects. You were spot on iSee, the plot thickens! ;-)

As pointed out, I'm using garbage collection so I'm not explicitly retaining or releasing objects.

I don't have a setListOfGreetings method as I'm using a property for this. In my MyDocument.h file I declare it like this:

@interface MyDocument : NSDocument
{
// Various declarations......
NSMutableArray *listOfGreetings;
}
@property(copy) NSMutableArray *listOfGreetings;

In MyDocument.m I'm using synthesize to generate the methods for me:

@implementation MyDocument
@synthesize listOfGreetings;

At one stage I commented out these lines and did it the old fashioned way but this didn't make any difference.

One other thing. I'm instantiating the array in the init method of MyDocument, like this:

-(id)init
{
self = [super init];
if (self) {
listOfGreetings = [[NSMutableArray] alloc] init];
}
return self;
}

Having read the Cocoa book by Aaron Hillegass, writing my own apps is a great way to really get my hands dirty and learn how Cocoa ticks. Your time spent helping is truly appreciated.

lucasgladding
Mar 4, 2008, 09:38 PM
This may just be a typo, but there is a problem in your creation of the array.

listOfGreetings = [[NSMutableArray] alloc] init];
- should be -
listOfGreetings = [[NSMutableArray alloc] init];

I have not yet played with garbage collection, but before ObjC 2 you would have had to retain this as well. I'm not sure if this is necessary (the other users in this thread seem to have some experience here).

Have you tried setting breakpoints in your table datasource methods?

Luke Gladding


Thanks very much for the helpful replies. I'll try to answer the questions asked above.

First of all, as suggested, I entered a simple line in loadDataRepresentation: and numberOfRowsInTableView to give the pointer to my object (using NSLog(@"%@", self); This gave the following output:

This is the log entry generated from loadData:
2008-03-04 22:35:20.577 TestApp[267:10b] <MyDocument: 0x1071990>
and this is the line generated from numberOfRows...:
2008-03-04 22:35:20.605 TestApp[267:10b] <MyDocument: 0x10728d0>

Looks like there's two MyDocument objects. You were spot on iSee, the plot thickens! ;-)

As pointed out, I'm using garbage collection so I'm not explicitly retaining or releasing objects.

I don't have a setListOfGreetings method as I'm using a property for this. In my MyDocument.h file I declare it like this:

@interface MyDocument : NSDocument
{
// Various declarations......
NSMutableArray *listOfGreetings;
}
@property(copy) NSMutableArray *listOfGreetings;

In MyDocument.m I'm using synthesize to generate the methods for me:

@implementation MyDocument
@synthesize listOfGreetings;

At one stage I commented out these lines and did it the old fashioned way but this didn't make any difference.

One other thing. I'm instantiating the array in the init method of MyDocument, like this:

-(id)init
{
self = [super init];
if (self) {
listOfGreetings = [[NSMutableArray] alloc] init];
}
return self;
}

Having read the Cocoa book by Aaron Hillegass, writing my own apps is a great way to really get my hands dirty and learn how Cocoa ticks. Your time spent helping is truly appreciated.

phjo
Mar 5, 2008, 01:31 AM
Just a shot in the dark, but these words of caution in the apple developers docs could be relevant :

5. If the objects on the document window require further outlets or actions, add these to the MyDocument subclass of NSDocument. Connect these actions and outlets via the File’s Owner icon on the Instances display of the nib file window.

Important: Do not generate an instance of MyDocument to make these connections.


from http://developer.apple.com/documentation/Cocoa/Conceptual/Documents/Tasks/ImplementingDocApp.html

phjo

AJClayton
Mar 5, 2008, 07:34 AM
Just a shot in the dark, but these words of caution in the apple developers docs could be relevant...

First of all, an apology to Luke. You're right - it was a typo in my post. I'm working on this app on my laptop and replied to the post from my desktop Mac re-keying the code snippets. Sorry for the confusion!

Thanks to phjo, your tip is what has resolved the problem for me. I'd made all of my connections in Interface Builder through My Document, but as soon as I changed all of them to File's Owner everything worked a treat!

Thanks to everyone who posted replies here, I've picked up some useful debugging tips along the way. I can now crack on with the rest of my project! :)

Cheers

Andy

kainjow
Mar 5, 2008, 07:57 AM
This may just be a typo, but there is a problem in your creation of the array.

listOfGreetings = [[NSMutableArray] alloc] init];
- should be -
listOfGreetings = [[NSMutableArray alloc] init];

I have not yet played with garbage collection, but before ObjC 2 you would have had to retain this as well.

That is incorrect actually. When you create an object with init (or any other init... method), it is already retained for you. If you retain again without a 2nd release, you will leak memory.

AJClayton, glad you got it working.

Eraserhead
Mar 5, 2008, 10:06 AM
This may just be a typo, but there is a problem in your creation of the array.

listOfGreetings = [[NSMutableArray alloc] init];

That is still wrong, you should use the initWithCapacity method when you initialise an NSMutableArray.

i.e. it should be:

listOfGreetings = [[NSMutableArray alloc] initWithCapacity:1];

AJClayton
Mar 5, 2008, 10:44 AM
That is still wrong, you should use the initWithCapacity method when you initialise an NSMutableArray.

i.e. it should be:

listOfGreetings = [[NSMutableArray alloc] initWithCapacity:1];


That's interesting. I've just read up on initWithCapacity as it wasn't something I'd heard of. Out of interest, how much difference does this *really* make? Is the default action of a plain "init" to reserve much more memory initially?

kainjow
Mar 5, 2008, 11:21 AM
That is still wrong, you should use the initWithCapacity method when you initialise an NSMutableArray.

i.e. it should be:

listOfGreetings = [[NSMutableArray alloc] initWithCapacity:1];


I've never heard this. I've used init all the time and never had issues. Do you have something to point to for reference?