我如何泄漏内存?

3
我有一个表格视图,在加载时创建一个人物对象。
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

在我的UITableView方法cellAtRowForIndexPath中,我正在创建每个人物对象并像这样分配图像属性...
Person *person = [[Person alloc] initWithUserName:userName];

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

当我在Instruments中运行时,它会突出显示NSData *data...这一行,并指出内存泄漏发生在那里。

为什么会在那里发生内存泄漏?

3个回答

5
首先,您需要了解实例变量和属性以及getter/setter之间的区别。
  • 实例变量(ivars)是存储在对象中的变量。您可以通过名称(例如“userName”)在方法内部访问ivar。
  • 属性定义了与对象的接口,允许读取和/或写入对象的信息。
  • getter/setter实现该接口,并且可以使用ivar作为后备存储。

您可以使用getter/setter来访问属性,显式地(例如[self userName])或(等效地)使用点语法self.userName。请注意,这两种表示法完全相同。您可以在对象的接口中使用@property声明属性(即,声明对象的接口),类似于:

@property (copy) NSString* userName;

这个声明本质上等同于输入以下内容:
- (NSString*) userName;
- (void) setUserName: (NSString*) theUserName;

你可以通过使用 @synthesize(它只是告诉编译器为你编写getter/setter),或者自己实现(即,为userName和setUserName编写方法实现)来实现属性。还有一个很少使用的第三个选项@dynamic,它告诉编译器你将在运行时处理这些方法,基本上只是消除了你否则会收到的警告。
接下来,你需要阅读并理解内存管理规则。它只有9个简短的段落,现在去读一下,我等等你。完成了?好的。
此外,你需要知道在init或dealloc例程中不应该使用getter/setter。
因此,你的init例程应该像这样:
- (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;
}

请注意,使用retain或copy来存储ivar中的每个值时,您需要拥有这些值的所有权。通常,对于NSString,您会使用copy将NSMutableStrings转换为您拥有的NSStrings,而不是使用retain,否则您将持有一个可能可变的字符串的引用。NSArray / NSDictionary也存在同样的问题,但我们假设TwitterHelper打算交出获取的数据。
您的dealloc方法必须释放各种ivars:
- (void)dealloc
{
    [userName release];
    [displayName release];
    [updates release];
    [userInfo release];
    [image release];
    [super dealloc];
}

在代码的其他地方,您应该使用self.userName来访问或更改属性,而不是直接访问ivars。

请注意,您可能考虑根本不存储displayName(以及类似的image),而只是实现一个从userInfo检索它的属性getter。为此,请删除displayName ivar,将属性更改为:

@property(readonly)NSString *displayName;

删除@synthesize displayName,并添加手动getter:

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

在dealloc中移除释放。

注意,您不需要保留/释放displayName中的值 - 您返回一个接收者不拥有的值,如果他们想要保留它,就由他们自己复制/保留它。


这是一个很好的答案,非常感谢。我唯一缺少的是对@property和@synthesize声明的理解。您能否在这方面提供更多见解?再次感谢。 - Jason

1
如果您选择创建一个属性,应该使用以下代码:
self.image = [NSURL URLWithString:[userInfo valueForKey:@"profile_image_url"]];

在你的init消息中,而不是


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

如果没有使用self前缀设置值,将不会调用copyretain消息,并且会创建一个内存问题(不一定是泄漏)。

这可能是Instruments指向的问题所在。

(显然,这适用于所有属性!)

或者,如果您不想使用访问器,则可以retaincopy检索到的值,例如:

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

2
不!在初始化程序或dealloc方法中,您不应该使用访问器(显式或通过点符号)。在这些方法中,应使用直接属性访问。在这两种情况下调用getter/setter可能会产生意想不到的(和不适当的)后果。但是,您应该复制NSURL。您应该重新阅读Cocoa内存管理指南。 - Barry Wark
@Jason:只有通过访问器访问成员变量时才会有影响。这意味着,在类内使用self来访问,否则就只是访问内部成员本身(毕竟它们名称相同)。 简言之,在你的代码示例中没有关系;但由于你没有调用retaincopy,你可能希望这个问题有所区别。 - Aviad Ben Dov
@Barry,Jason:以防万一,手动保留/复制值的替代方案已添加。 - Aviad Ben Dov
顺带一提,正在重新阅读内存管理指南。相比第一次刚开始接触时,现在更加理解其中的意义了。当时对一切都是全新的东西感到有些困惑。 - Jason
@Barry - 尽管文档上是这么说的,但我不相信在初始化器中使用合成访问器会产生意想不到的后果。例如,新的 ObjC 运行时允许您定义没有对应 ivar 的属性,这意味着初始化属性的唯一方法是通过访问器!如果使用访问器有“意想不到的后果”,那么您将无法正确地初始化该属性。 - Dave DeLong
显示剩余8条评论

0
您正在调用 Person 上的 alloc 方法,但未将其释放。您泄漏了您的 person 对象。(在您的单元格配置中)

抱歉,我在提供的代码中漏掉了[人员发布]方法。它已经在里面了。而且,如果是这种情况,Instruments不会指向那一行吗? - Jason
啊,是的。你试过用Clang静态分析器运行你的代码吗?如果你以前从未使用过它,请尝试在Google上搜索“AnalysisTool”,它本质上是一个图形界面前端(因此更加友好),可以让它分析你的项目。它不仅能找到泄漏,还可以一步一步地向你展示。这是一个非常棒的工具。 - jbrennan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接