views:

447

answers:

3

I have a table view that when loading creates a person object

Person.h

#import <UIKit/UIKit.h>
#import "TwitterHelper.h"

@interface Person : NSObject {
    NSDictionary *userInfo;
    NSURL *image;
    NSString *userName;
    NSString *displayName;
    NSArray *updates;
}
/*
@property (retain) NSString *userName;
@property (retain) NSString *displayName;
@property (retain) NSDictionary *userInfo;
 */
@property (nonatomic, copy) NSURL *image;
@property (retain) NSArray *updates;

- (id)initWithUserName:userName;

@end

Person.m

#import "Person.h"


@implementation Person

/*
@synthesize userName;
@synthesize displayName;
@synthesize userInfo;
 */
@synthesize image;
@synthesize updates;

- (id)initWithUserName:(NSString *)user{

    userName = user;
    userInfo = [TwitterHelper fetchInfoForUsername:user];
    displayName = [userInfo valueForKey:@"name"];
    image = [NSURL URLWithString:[userInfo valueForKey:@"profile_image_url"]];
    updates = [TwitterHelper fetchTimelineForUsername:userName];

    return self;
}

- (void)dealloc
{
    /*
    [userName release];
    [displayName release];
    [updates release];
     [userInfo release];
     [image release];
     */
    [super dealloc];
}

@end

Inside my UITableView method cellAtRowForIndexPath I am creating each person object and assigning the image property like so...

Person *person = [[Person alloc] initWithUserName:userName];

NSData *data = [[NSData alloc] initWithContentsOfURL:person.image];
[data release];

When I run this in Instruments it highlights the NSData *data... row saying that is where the leak is.

Why is it leaking there?

+1  A: 

If you choose to create a property, you should use:

self.image = [NSURL URLWithString:[userInfo valueForKey:@"profile_image_url"]];

in your init message and not

image = [NSURL URLWithString:[userInfo valueForKey:@"profile_image_url"]];

Setting the value without the self prefix will not call the copy or retain message, and will create a memory problem (not necessarily a leak).

This might be what Instruments is pointing you to.

(This obviously applies to all properties!)

Alternatively, if you don't want to use the accessor, then retain or copy the value retrieved, e.g.:

image = [[NSURL URLWithString:[userInfo valueForKey:@"profile_image_url"]] retain];
Aviad Ben Dov
does the way you declare the property (atomic, retain, copy, etc...) make any difference?
Jason
well, I should say I know it makes a difference, but in my above example, what *should* it be
Jason
No! You should not use accessors (explicitly or via dot notation) in your initializer or in your dealloc method. Use direct property access *in these methods*. Calling the getter/setter may have unintended (and inappropriate) concequences in these two situation.sYou *should* be copying the NSURL, however. You should re-read the Memory Management Guide for Cocoa.
Barry Wark
@Barry: I never noticed such a rule, not in any guide until now and going through the management guide for cocoa again I didn't see it either (maybe I missed it?) Could you point it out to me?
Aviad Ben Dov
@Jason: It does make a difference only if you access the member via the accessor. That means, using it through `self` if inside the class. Otherwise, it's just accessing the internal member itself (they have the same name after all).In short, in your code example, it doesn't matter; but since you didn't call `retain` or `copy`, you probably want it to matter.
Aviad Ben Dov
@Barry, Jason: Just in case, added the alternative of retaining/copying the value manually.
Aviad Ben Dov
Aviad, sorry I conflated two issues. Jason should reread the memory guide. You are correct that, as written, direct assignment to image is incorrect, but as I said, you probably shouldn't use the setter in the object initializer. This is not laid out very clearly in any documentation, but has been repeated several times by Cocoa engineers on the cocoa-dev list and here on Stack Overflow.
Barry Wark
In the alternative, if I leave out the retain and use retain on the @property would that be the same?
Jason
btw, re-reading the memory management guide. It makes more sense then the first time I read it when everything was brand new
Jason
If you move `retain` to the property, then the retain will only take effect if you use the property's accessor; i.e. `self.image` instead of `image`, which you shouldn't be doing in the initializer.
John Calsbeek
@John then how should I declare the variable in the @property?
Jason
@Barry - While the docs say that, I can't believe that using synthesized accessors in initializers would have unintended consequences. For example, the new ObjC runtime allows you to define properties with no corresponding ivar, which means the only way to initialize the property is through an accessor! If using accessors has "unintended consequences", then you wouldn't be able to initialize the property properly.
Dave DeLong
@Dave - even with @synthesized properties, you can still get unpleasant consequences if either a subclass overrides the properties, or if any object is observing the properties. These are the two most obvious reasons why using properties in init/dealloc is not recommended. And you are correct, there is no other solution in the case of new runtime magically created ivars, which is more an argument against using them until the issue is resolved than it is a reason to ignore the potential risks of these unintended consequences.
Peter N Lewis
+4  A: 

First, you need to understand the difference between instance variables and properties and getter/setters.

  • instance variables (ivars) are variables stored in your object. You access an ivar from within a method simply by naming it (eg "userName").
  • properties define an interface to your object, allowing information to be read and/or written to your object.
  • getters/setters implement that interface and may use an ivar as backing storage

You access a property by using a getter/setter, either explicitly (eg [self userName]) or (equivalently) using dot syntax self.userName. Note that these two notations are exactly identical. You declare a property (ie, you declare an interface to your object) using @property in the interface of your object, something like:

@property (copy) NSString* userName;

This declartion is essentially equivalent to typing:

- (NSString*) userName;
- (void) setUserName: (NSString*) theUserName;

You implement a property, either by using @synthesize (which simply tells the compiler to write the getter/setter for you) or by implementing it yourself (ie, you write methods implementation for userName and setUserName). There is also a rarely used third option, @dynamic, which tells the compiler you will handle the methods at run time, essentially just silincing the warning you would otherwise get.

Next, you need to read and understand the memory management rules. Its only 9 short paragraphs, go read it now, I'll wait. Done? good.

Further, you need to know that you should not use getters/setters in either the init or dealloc routines.

So your init routine should look something like this:

- (id)initWithUserName:(NSString *)user{
    userName = [user copy];
    userInfo = [[TwitterHelper fetchInfoForUsername:user] retain];
    displayName = [[userInfo valueForKey:@"name"] copy];
    image = [[NSURL URLWithString:[userInfo valueForKey:@"profile_image_url"]] copy];
    updates = [[TwitterHelper fetchTimelineForUsername:userName] retain];
    return self;
}

Note that you take ownership of each value you store in an ivar with retain or copy. Generally, you use copy for NSString to convert an NSMutableStrings into NSStrings you own, rather than retain which would leave you holding a reference to a possibly mutable string. The same issue applies to NSArray/NSDictionary, but we will assume TwitterHelper intends to hand off the fetched data.

Your dealloc will have to release the various ivars:

- (void)dealloc
{
    [userName release];
    [displayName release];
    [updates release];
    [userInfo release];
    [image release];
    [super dealloc];
}

Anywhere else in your code you would use self.userName to access or change the properties, rather than access the ivars directly.

Note that you might consider not storing the displayName (and similarly image) at all, but simply implement a property getter that retrieves it from userInfo. To do this, delete the displayName ivar, change the property to:

@property (readonly) NSString *displayName;

remove the @synthesize displayName, and add a manual getter:

- (NSString*) displayName
{
    return [userInfo valueForKey:@"name"];
}

and remove the release in dealloc.

Note that you do not need to retain/release the value in displayName - you return a value that the receiver does not own and it is up to them to copy/retain it if they want to keep it.

Peter N Lewis
This has been a great answer, thank you very much. The only thing left that I am missing is the understanding of the @property and @synthesize declarations. Could you provide some more insight in that area? Thanks again.
Jason
A: 

You are calling alloc on Person but not releasing it. You've leaked your person object. (in your cell configuration)

jbrennan
sorry, I left the [person release] method out of my provided code. It is in there. Also, if that were the case, wouldn't Instruments point me to that line?
Jason
Ah, yes. Have you tried running your code through the Clang Static Analyzer? If you've never used it before, try Googling for "AnalysisTool", which is essentially a graphical frontend (and thus gentler), and lets it analyze your project. It will not only find leaks, but also show them to you step by step. It's a fantastic tool.
jbrennan