NSMutableArray的正确声明、分配、加载和释放方式

6
我在*.h文件中声明了我的数组:
@interface aViewController: UIViewController
{
   NSMutableArray *anArray;    // 你需要稍后多次更改它。
}
@end
我在*.m文件中为其分配内存:
-(void) viewDidLoad
{
   anArray = [[NSMutableArray alloc] init];
}
我点击测试按钮来加载我的数组(最终每次单击都需要加载不同的值):
   anArray = [NSMutableArray arrayWithObjects:@"one", @"two", @"three", nil];
我在这里释放它:
-(void) dealloc
{
   [anArray release];
   [super dealloc];
}
看起来一切都没问题吗?
因为当我稍后运行此代码时,它会崩溃:
   NSLog(@"%d", [anArray count]);
不确定为什么一个简单的“NSLog()和count”会导致一切崩溃。

Dirk,

让我这样说:我对指针、数组、字符串和内存有一个巨大的误解

我已经阅读了我能找到的所有内容...但是(尚未)找到一个简单、清晰、易于理解的描述。

你能建议一个吗?(希望不超过10页的阅读量。) 有没有一个只解释这个主题的参考资料...从“你有12年的编程经验...但从未涉及分配内存或指针”的角度出发?

那么变量名不是我引用对象的方式吗? 那为什么还要有它呢?

我习惯了许多其他语言,只需要这样做:

myString = "this"
myString = "that"
myInt = 5 myInt = 15

(还有什么比这更简单的呢。)


在我看来,这似乎是最简单的方法。 (而且它似乎有效。但这是真正正确的吗?)

不要初始分配任何内存给我的NSMutableArray。
不要重复重新分配任何内存。(当我改变数组的值时。)
不要重复释放它。(在我改变数组的值之前。)
不要在退出程序时释放它。
但是: 始终记得在最初分配(和重复分配)新值给我的anArray变量时使用RETAIN。

您没有使用以下代码加载数组: anArray = [NSMutableArray arrayWithObjects:@"one", @"two", @"three", nil]; 相反,您正在替换它为一个新实例,更糟糕的是:使用某个您无法控制的实体的引用拥有该实例的引用

哇。所以我可以有20个数组...都叫做相同的名称:anArray...它们都是不同的吗?(没有全局数组这样的东西吗?)

为了清除旧值,可能会用到removeAllObject方法。还有一些变异方法,可以一次添加多个值。
所以...首先我要“删除所有对象”...然后我才能调用一个方法将所有新值重新添加。
anArray = [NSMutableArray arrayWithObjects:@“one”,@“two”,@“three”,nil]保留; 而不是使用alloc / init序列。
哇。 我以为没有分配空间就不能存储任何东西在数组中。
如果您真的打算替换整个数组,您可能希望考虑使用属性。如何使用属性?这样做的正确方式是什么:
anArray = [NSMutableArray arrayWithObjects:@“one”,@“two”,@“three”,nil]; anArray = [NSMutableArray arrayWithObjects:@“four”,@“five”,@“six”,nil];
就像我会做的那样:
x = 12; x = 24;
哇。 我真的完全误解了关于字符串,数组和内存的所有内容。我以为“简单的方法”是一次分配空间......使用可变数组......尽可能多地更改它......并一次释放它。
这样做的问题是,这个新数组没有被保留,
我认为旧数组会消失......然后可以使用新数组。 (但我想不是。)
此外,您有一个内存泄漏,因为您从未释放原始数组。
我认为旧数组不应该被释放......我没结束......我只是希望将其更改为包含我的新值。 (但我想不是。)
但是有一种方法是使用[anArray release];
所以我必须“保留”它......这样我才不会失去它? (不确定为什么会这样。直到我告诉它......在我的最后dealloc电话中。)
另一种可能更正确的修复方法是使用addObject:或addObjectsFromArray:NSMutableArray方法,而不是不断创建新数组。
我只想创建一个数组......并根据需要使用它。 我永远不想添加到数组中。 我要将其设置为我的新值。

我知道这篇文章在互联网时间上已经过时了,但我想澄清一下,在Cocoa开发中,我们基本上从来不会真正操作数据结构本身,而是操作指向它们的指针。时刻牢记这一点,并且要注意指针赋值时需要非常小心。 - rwols
我希望我能给你超过一个赞 - 我正在遇到完全相同的问题 - Objective C 是超乎想象的反直觉。 - Jimmery
3个回答

11

你没有加载你的数组

anArray = [NSMutableArray arrayWithObjects:@"one", @"two", @"three", nil];

你现在替换了它,而且更糟糕的是: 你使用的实例的引用实际上归某个你无法控制的实体所有(很可能是一个NSAutoreleasePool)你正确拥有的引用是由...

[[NSMutableArray alloc] init]

数据已经丢失并将被泄露。

不要替换整个数组引用,而是改变您已经拥有的可用对象,例如使用addObject:方法。

[anArray addObject: @"one"]
[anArray addObject: @"two"]

为了清除旧值,可以使用方法removeAllObjects。还有一些变异方法,可以一次添加多个值。

另外,您可以使用已经使用的构建方法来分配数组,但要小心保留它。在viewDidLoad中执行。

anArray = [[NSMutableArray arrayWithObjects:@"one", @"two", @"three", nil] retain];

可以考虑使用属性替代alloc/init序列。如果确实打算替换整个数组,您可能希望考虑使用属性而不是手动引用计数。


4
问题在于anArray = [NSMutableArray arrayWithObjects:@"one", @"two", @"three", nil];这会替换掉你最初创建的数组。这样做的问题在于这个新数组没有被保留,所以一旦方法返回,你就会失去它。此外,你还有一个内存泄漏问题,因为你从未释放原始数组。
有几种解决方法,但其中之一是使用[anArray release]; anArray = [[NSMutableArray arrayWithObjects:@"one", @"two", @"three", nil] retain];
另一种可能更正确的解决方法是使用addObject:addObjectsFromArray:NSMutableArray方法,而不是不断地创建新数组。

3
记住这个简单的内存规则:只释放你拥有的对象。只有在使用以下方法创建它们时才拥有对象:
  • init (此方法创建一个保留计数为1的新对象)
  • new (此方法与使用alloc和init相同)
  • copy (此方法使用方法接收器的内容创建一个保留计数为1的新对象)
  • retain (此方法将保留计数增加1)
系统会自动释放保留计数为零的对象。在完成使用后,您必须释放您拥有的每个对象。如果您过早地释放,您就会遇到危险的情况。如果您在完成使用后不释放对象,则会出现泄漏。
指针是一个特殊的对象,它引用内存中的某些对象。它基本上是一个内存地址(和一些其他数据)。如果要使用对象,则必须为其分配内存,然后进行初始化。您可以通过使用=符号来分配指针。
string = [[NSString alloc] init];

string现在的保留计数为1。但由于NSString是一个不可变对象,它在初始化后就无法更改。赋值string的一种方法是在初始化时完成。

string = [[NSString alloc] initWithString: @"Hello, World!"];

如果您需要经常更改字符串,可以使用另一个可变的类:NSMutableString。但是,仍然只能通过消息来更改它们。

string = [[NSMutableString alloc] initWithString: @"Initial string"];
[string setString: @"Modified string"];

请注意,以下代码错误,会导致内存泄漏。

string = [[NSMutableString alloc] initWithString: @"Initial string"];
string = @"Modified string";

在第一行中,将string分配给一个新创建的对象。在第二行中,string被分配给另一个字符串。你失去了对新创建对象的引用,导致内存泄漏:你无法释放没有引用的对象。
当你使用整数(int类型)时,不会出现问题,因为它是一种“本地”对象。Objective-C运行时直接将这些数据类型与它们的值相关联,而不是指针。
int1 = 4;
int1 = 5;

注意1:不要忘记始终告诉运行时你的指针类型。如果你想使用string,你必须先在头文件中定义它(如果它是公共的),或者在实现文件中定义它(如果你想将其隐藏在其他方法中)。然后你就可以自由地使用它的名称。

NSString *string;

星号告诉运行时它是一个指针。对于本地类型,没有指针,因此您会得到这个结果。
int int1;

注2: 数组(还有字典等)的行为与 NSString 相似,因为它们都有不可变和可变两种版本。

注3: 大多数类都有特殊方法,可以一次性分配、初始化并自动释放内存。

NSArray *myArray = [NSArray arrayWithObjects: @"Butter", @"Milk", @"Honey", nil];

一个自动释放的对象不需要手动release,因为系统会在一段时间后为您release它。在您仅需要对象在方法或方法之间存在的情况下,而不是作为对象的永久部分时,这非常方便。当您在主线程(在具有GUI的应用程序中)上autorelease某些对象时,它将在事件循环完成当前循环时被release(当处理某些事件时,它会完成)。有关Autorelease Pools的更多信息,请阅读Apple文档。

我希望我可以帮助那些不太了解指针的人。我自己也遇到了几个月的问题,直到我进行了大量实验。 :-)


你需要循环遍历数组中的所有元素并在每个元素上调用 release 吗? - tivo
如果你使用 ARC(在 Xcode 中是默认的),你根本不需要使用 release。;-) - Constantino Tsarouhas

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