2009-12-24 135 views
1

我有一个非常明确的问题:iPhone内存管理(专为属性)

//.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-> // //保留测试的计数为1并且copyString为2.由于两者都保存不同的内存位置。但是我们可以写[copyString release]。

回答

1

如果你有关键字'retain'或'copy'定义的属性,你应该总是在dealloc方法中释放相应的成员变量。

在这种情况下,你的dealloc应该是这样的:

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

    [super dealloc]; 
} 

现在,当你alloc在自定义类的方法的字符串,这种方法拥有作为内存管理编程指南中描述的字符串可可。这意味着你应该在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

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初始呼叫建立与保留计数1,符合市场预期新的NSMutableString对象。我们有一个保留在其上的对象。
  • 编辑retain ed属性会按预期递增保留计数。我们有一个有两个保留的对象。

这是它变得奇怪的地方。 copy属性的赋值的确确实实施了一个副本,但并不如预期的那样。 NSString和NSMutableString是类集群的一部分 - 当您创建或修改字符串时,它可能是也可能不是您期望的类的实例。该语言可能会将其变为幕后的其他表示形式。

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

所以现在我们有两个对象:我们原来的test对象被保留两次,而新对象是静态的,因此保留计数为INT_MAX。最后,由于新字符串是不可变的,因此调用它的增变器方法会杀死程序。另外,将原来的电话从init更改为initWithString:的确会使复制分配按预期执行(有点) - 复制对象上的保留计数仅为1,但仍不能对其进行变异。再说一次,这可能是由于编译器中的一些优化魔法,它决定该字符串是静态的,并且如果没有必要,没有理由使其变为可变的。

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

有关这一切的更多信息,可以阅读:

+0

很好的答案,Tim。 – gavinb 2009-12-24 10:29:03

+2

不需要为这种情况下的编译器优化。该文档指出,复制属性属性调用对象的复制方法。定义此方法的NSCopying协议声明它将返回一个不可变对象(如果适用于该类)。要获得可变副本,该属性将不得不调用mutableCopy。因此,复制属性将始终复制一个不可变的对象,除了实现调用mutableCopy的自己的setter外,目前还没有办法解决这个问题。 – Adrian 2009-12-24 21:29:59

+0

Adrian:谢谢你的信息! – Tim 2009-12-25 05:40:52