views:

43

answers:

1

Hi - I've created an object called 'DateTracker' which conforms to NSCoding, so it contains encodeWithCoder and initWithCoder methods. When I initialise it, I call the following:

DateTracker *currentTracker = [[DateTracker alloc] initFromFile];

The initFromFile method looks like this:

- (id)initFromFile { 
    NSString *filePath = [self dataFilePath];
    if ([[NSFileManager defaultManager] attributesOfFileSystemForPath:filePath error:NULL]) {
        NSData *data = [[NSMutableData alloc] initWithContentsOfFile:filePath];
        NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
        self = [unarchiver decodeObjectForKey:kDateDataKey];
        [unarchiver finishDecoding];
        [unarchiver release];
        [data release];
    }
    return self;
}

However when I try to call

[currentTracker release];

my app crashes.

When I run the app with performance tools to check for memory leaks, it complains that I'm not releasing this object.

Any ideas what I'm doing wrong?

Thanks :)

Michael

+2  A: 

This line:

self = [unarchiver decodeObjectForKey:kDateDataKey];

is going to give you problems.

What you're doing is to allocate a DateTracker object ([DateTracker alloc]), then create a new DateTracker object (by -decodeObjectForKey:) and make the "self" pointer refer to the new object. There are two problems with that:

  • you no longer have a reference to the old object, so it's leaked
  • the new object is not retained, so it goes away (or causes a crash if you try to release it)

I would say the approach of having an object replace itself is a bit suspect. Perhaps you would do better to move the filePath variable outside of the DateTracker object, and unarchive it by something like:

DateTracker *currentTracker = [[DateTracker unarchiveFromFile:filePath] retain];

where unarchiveFromFile: is a class method that does essentially what initFromFile did, without messing with self:

+ (DateTracker*)unarchiveFromFile:(NSString *)filePath { 
    DateTracker *result = nil;
    if ([[NSFileManager defaultManager] attributesOfFileSystemForPath:filePath error:NULL]) {
        NSData *data = [[NSMutableData alloc] initWithContentsOfFile:filePath];
        NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
        result = [unarchiver decodeObjectForKey:kDateDataKey];
        [unarchiver finishDecoding];
        [unarchiver release];
        [data release];
    }
    return result;
}
David Gelhar
Aaaaah I see. Would it still work if I kept the file path initialiser inside the DateTracker class though? So the first line of the unarchiveFromFile method would get the filePath string.
Smikey
Also, what would happen if I just changed my initFromFile method into a class method and called it like so: DateTracker *currentTracker = [DateTracker initFromFile]. Would it autorelease when it wasn't being used? Or should I explicitly retain and release it? Or would that just be a bad idea anyway...
Smikey
Sure, `filePath` could be a static member of the DateTracker class. The point about autorelease is that `decodeObjectForKey:` is going to return an autoreleased object (according to the normal Cocoa memory management conventions, since it doesn't have "Alloc" or "Copy" in its name, you know that it returns an autoreleased object). So, you need to retain/release it if you want to hang on to it.
David Gelhar
Ah great, thanks for the explanation! So what happend when I create say a UIImage as follows: UIImage *image = [UIImage imageNamed:@"theImage.png"]; Since it's a class method, it's autoreleased right? But if I want to use it again I should add a retain message. But if I don't add a retain message, is there no need to call [image release]? Thanks for the help btw!
Smikey
Also, I'm a little unclear about class methods. I can't use class variables or refer to other class methods within them? So to initialise the correct filePath, I either need to pass it as an argument, or set it as a static variable as you suggest. But if I set it as a static variable, I can't be sure the file system won't change and it will be incorrect. And if I pass it as an argument, I might initialise it incorrectly in another class. My dataFilePath method currently resides inside the DateTracker class, so I can be sure it will always return the same result...
Smikey
In this example: `UIImage *image = [UIImage imageNamed:@"theImage.png"];` you know that `image` is autoreleased, not because `+imageNamed` is a class method, but because the method name does not contain `Copy` or `Alloc`. It's possible to have a class method that allocates an instance that you **do** need to release, an example of such a method is `alloc` :-)
David Gelhar
Ah ok great, thanks for the explanation. So I guess my only remaining problem is how to ensure I pass the correct filePath to my class initialiser. In DateTracker, my saveToFile method calls the method dataFilePath to determine where to save the file, and the initFromFile called the same method to ensure it got the same path. But now that I can't call [self dataFilePath] from within the class initialiser, I call dataFilePath from another class and pass the result as an argument. Is the only way to ensure I have the correct file name to #define constants and make sure they're the same?
Smikey
A "static" variable in C (and Objective-C) does not mean that the value can't change, it just means there's a single copy of the variable that exists permanently. So you can declare `static NSString *filePath` inside DateTracker.m, write a `+setFilePath:` class method that sets the file path, and refer to that filePath within your `+initFromFile` class method. (That assumes you're reading all objects from the same file. If not, then you would want to pass the desired path as an argument each time you call `+initFromFile:`
David Gelhar
Aaaah ok. Would it also be ok to create a class method +(NSString *)dataFilePath which is basically the same as -(NSString *)dataFilePath and call that in the +(DateTracker *)unarchiveFromFile method? I'm sorry I'm so muddled over all of this and thanks for your patience explaining it. I guess it comes from being self taught and not quite grasping some of the fundamental concepts. I've written a huge program, but sometimes it feels like it's held together with bits of string. And it's pretty leaky :o
Smikey
Yes, it sounds like that should work.
David Gelhar