iPhone内存管理(特别针对属性)

时间:2009-12-24 09:25:41

标签: iphone cocoa cocoa-touch

我有一个非常明确的问题:

//.h file

@property (nonatomic, retain)NSMutableString * retainString;
@property (nonatomic, copy)NSMutableString * copyString;

//.m file
@synthesis retainString, copyString;
-(void)Process
{
  NSMutableString *test = [[NSMutableString alloc]inti];//retain count should be 1

  self.retainString = test;

  self.copyString = test;

}

COND。 1→ //两者的保留计数应为2.因为它们指向具有保留计数2的相同内存位置,所以应该如何写入释放。

COND。 2→ // //保留test的计数为1,copyString为2.由于两者都保持不同的内存位置。但我们可以写[copyString release]。

3 个答案:

答案 0 :(得分:9)

这个设置实际上做了一些非常有趣的事情,并提出了一些关于Objective-C内存管理的好点。我们首先重申一下代码:

// Testing.h
@interface Testing : NSObject {
    NSMutableString *retainString;
    NSMutableString *copyString;
}

@property(nonatomic,retain) NSMutableString *retainString;
@property(nonatomic,copy) NSMutableString *copyString;
// Testing.m
@implementation Testing

@synthesize retainString, copyString;

- (id)init {
    if(self = [super init]) {
        NSMutableString *test = [[NSMutableString alloc] init];
        NSLog(@"test %d; retain %d; copy %d", [test retainCount], [retainString retainCount], [copyString retainCount]);
        self.retainString = test;
        NSLog(@"test %d; retain %d; copy %d", [test retainCount], [retainString retainCount], [copyString retainCount]);
        self.copyString = test;
        NSLog(@"test %d; retain %d; copy %d", [test retainCount], [retainString retainCount], [copyString retainCount]);
        [self.copyString appendFormat:@"test"];
        NSLog(@"test %d; retain %d; copy %d", [test retainCount], [retainString retainCount], [copyString retainCount]);
    }
    return self;
}

@end

这会产生日志输出:

2009-12-24 03:35:01.408 RetainCountTesting[1429:40b] test 1; retain 0; copy 0
2009-12-24 03:35:01.410 RetainCountTesting[1429:40b] test 2; retain 2; copy 0
2009-12-24 03:35:01.410 RetainCountTesting[1429:40b] test 2; retain 2; copy 2147483647
2009-12-24 03:35:01.413 RetainCountTesting[1429:40b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to mutate immutable object with appendFormat:'

那么这里发生了什么?前两个调用相当简单:

  • alloc / init的初始调用会按预期创建一个新的NSMutableString对象,其保留计数为1。我们有一个对象,其中一个保留在其上。
  • retain ed属性的赋值会按预期增加保留计数。我们有一个对象有两个保留。

这就是奇怪的地方。对copy属性的赋值确实会复制,但不会按照您期望的方式进行。 NSString和NSMutableString是所谓的类集群的一部分 - 当您创建或修改字符串时,它可能是也可能不是您期望的类的实例。该语言可能会将其变为幕后的其他表现形式。

在这种特殊情况下,当执行复制时,显然语言决定字符串(因为它不包含任何信息)被认为是不可变的,并且这样做。当人们做[[NSString alloc] initWithString:@"hello"]之类的事情时经常会出现这种情况 - 它是一个常量的静态字符串,所以不需要动态分配对象。保持静态有助于运行时更好。

所以现在我们有两个对象:保留两次的原始test对象,以及静态的新对象,因此保留计数为INT_MAX。最后,由于新字符串是不可变的,因此在其上调用mutator方法会终止该程序。

顺便说一下,将原始调用从init更改为initWithString:会使复制分配按预期执行(稍微) - 您只能在复制的对象上获得保留计数1,但是仍然无法改变它。同样,这可能是由于编译器内部的一些优化魔法决定了字符串是静态的,并且没有理由让它变得可变,如果它没有。

要回答您的最终问题:,您可以在其中任何一个对象上调用release。它不会做太多。充其量,你将销毁复制的对象(因为它的保留计数为1);在最坏的情况下,它对静态字符串对象没有任何作用。但是,我建议继续处理属性:而不是释放复制的对象,为什么不只是self.copyString = nil;?因为它调用属性setter,所以它会根据需要处理释放,然后你没有指向仍然漂浮的对象的指针。

有关所有这些的更多信息,请考虑阅读:

答案 1 :(得分:1)

如果您使用关键字“retain”或“copy”定义了属性,则应始终在dealloc方法中释放相应的成员变量。

在这种情况下,您的dealloc应如下所示:

- (void)dealloc {
    [retainString release];
    [copyString release];

    [super dealloc];
}

现在,当您在自定义类'方法中alloc一个字符串时,此方法拥有内存管理编程指南(适用于Cocoa)中所述的字符串。这意味着在离开方法之前你应该release字符串。

@synthesize retainString, copyString;

- (void)Process {
    NSMutableString *test = [[NSMutableString alloc] init]; //retain count is 1

    self.retainString = test;     // retain count of test is 2
    self.copyString = test;       // test's retain count = 2, copyString's = 1

    [test release];               // retain count of test is 1 again
}

当销毁此类的实例时,将调用dealloc方法,这将依次release两个字符串。一旦他们的保留计数保持为1,他们也将dealloc'ed

答案 2 :(得分:0)

这也可能有所帮助 NSString property: copy or retain?