【问题标题】:How am I leaking memory?我如何泄漏内存?
【发布时间】:2010-11-19 00:37:52
【问题描述】:

我有一个表格视图,在加载时会创建一个人对象

人.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

人.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... 行,说明这是泄漏的位置。

为什么会漏水?

【问题讨论】:

    标签: iphone objective-c cocoa memory-leaks cs193p


    【解决方案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];
    

    【讨论】:

    • 您声明属性的方式(原子、保留、复制等)有什么不同吗?
    • 好吧,我应该说我知道这会有所不同,但在我上面的例子中,应该是什么
    • 不!您不应在初始化程序或 dealloc 方法中使用访问器(显式或通过点表示法)。 在这些方法中使用直接属性访问。在这两种情况下,调用 getter/setter 可能会产生意想不到的(和不适当的)后果。但是,您应该复制 NSURL。您应该重新阅读 Cocoa 的内存管理指南。
    • @Barry:我从来没有注意到这样的规则,直到现在都没有在任何指南中,并且再次通过可可管理指南我也没有看到它(也许我错过了它?)你能指出交给我了吗?
    • @Jason:只有当您通过访问器访问成员时,它才会有所作为。这意味着,如果在课堂内,通过self 使用它。否则,它只是访问内部成员本身(毕竟它们具有相同的名称)。简而言之,在您的代码示例中,没关系;但既然你没有打电话给retaincopy,你可能希望它很重要。
    【解决方案2】:

    首先,你需要了解实例变量和属性与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,它告诉编译器您将在运行时处理这些方法,基本上只是消除您可能会收到的警告。

    接下来,您需要阅读并理解memory management rules。它只有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;
    }
    

    请注意,您通过保留或复制获得存储在 ivar 中的每个值的所有权。通常,您使用 NSString 的副本将 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(以及类似的图像),而只需实现一个从 userInfo 检索它的属性 getter。为此,请删除 displayName ivar,将属性更改为:

    @property (只读) NSString *displayName;

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

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

    并删除 dealloc 中的释放。

    请注意,您不需要保留/释放 displayName 中的值 - 您返回一个接收者不拥有的值,如果他们愿意,他们可以复制/保留它保留它。

    【讨论】:

    • 这是一个很好的答案,非常感谢。我唯一缺少的是对@property 和@synthesize 声明的理解。您能否提供更多关于该领域的见解?再次感谢。
    【解决方案3】:

    你在 Person 上调用 alloc 但没有释放它。你泄露了你的person 对象。 (在您的单元格配置中)

    【讨论】:

    • 抱歉,我在提供的代码中遗漏了 [person release] 方法。它在里面。另外,如果是这样的话,Instruments 不会把我指向那条线吗?
    • 啊,是的。您是否尝试过通过 Clang 静态分析器运行您的代码?如果您以前从未使用过它,请尝试用谷歌搜索“AnalysisTool”,它本质上是一个图形前端(因此更温和),并让它分析您的项目。它不仅会发现漏洞,还会一步一步地向您展示。这是一个很棒的工具。
    猜你喜欢
    • 2019-07-17
    • 1970-01-01
    • 1970-01-01
    • 2011-05-28
    • 1970-01-01
    • 1970-01-01
    • 2011-08-21
    • 2011-01-12
    • 2015-06-28
    相关资源
    最近更新 更多