访问实例变量的语法是什么?(Objective-C)

11
什么是在Objective-C中访问实例变量的正确语法?
假设我们有这个变量:
@interface thisInterface : UIViewController {
    NSMutableString *aString;
}

@property (nonatomic, retain) NSMutableString *aString;

而且它是合成的。

当我们想要访问它时,首先需要分配和初始化它。在学习 Objective-C 大约一个月后,我看到了两种不同的语法形式。有些人只是这样分配字符串:aString = [[NSMutableString alloc] initWithString:@"hi"],他们像那样分配字符串;我也看到有人从self.aString开始,然后继续初始化它们的实例变量。我想我只是想知道初始化实例变量的最恰当方式是什么,因为在前面的示例中,我收到了 EXC_BAD_ACCESS 错误。但在加入 self. 后就没有出现这个问题了。

如果这是一个重复的问题,请原谅我,但在阅读了一些 SO 帖子后,我变得好奇了。我正在尝试学习 Objective-C 的正确语法,因为我更喜欢规范而不是马虎。


2
在处理字符串时,应该使用复制(Copy)而不是保留(Retain)。 - Simon Lee
7个回答

16
如果您已经声明了一个属性并在.m文件中使用@synthesize合成它,那么您可以通过以下方式进行设置:
self.aString = @"hi"; // or [[NSMutableString alloc] initWithString:@"hi"];

使用 self.varName 可以利用你的属性声明所做的工作——它会为你处理新值的保留(因为你的属性有 retain 属性),释放旧值等等。

如果你只是这样做:

aString = someValue;

你可能正在泄露原来存在于aString中的值,因为如果不使用self.aString,你是直接访问变量而不是通过属性进行访问。


这正是我一直在寻找的!我想这与保留属性有关。因此,当直接给ivar赋值时,我们不释放旧值(因为这就是retain属性的作用,对吗?),这可能会导致一些泄漏。在分析我的代码之前,我只想确保这是正确的。 - SpacePyro
1
是的,如果您直接修改实例变量,那么您将绕过属性的合成访问器代码。这可能会导致泄漏、早期释放等问题。通常情况下,这不是您想要的结果。 - Steve N
你应该意识到,如果这个属性有一个retain属性,那么self.aString = [[NSMutableString alloc] initWithString:@"hi"];就会造成内存泄漏。 - Simon Lee
我明白了。非常感谢,你的解释简明易懂!:) 我同意@Simon Lee的观点,因为它会被保留两次。 - SpacePyro
是的-如果您分配内存而不使用已自动释放的方便方法,则@Simon Lee是正确的。 - Steve N
那不是实例变量,那是属性。两者不同。 - Alex Zavatone

12
请注意 self->varNameself.varName 之间的区别。
第一个是指针访问。第二个是属性访问。
为什么这很重要?指针访问是直接的。另一方面,属性访问使用 getter 和 setter(无论它们是否被 @synthesized 修饰)。此外,作为一种便利,@synthesized 访问器会为您处理内存管理(即使用 self.varName = ...; 时),而 varName = ...; 只做它所说的事情,即赋值 -> (这就是您可能遇到的 EXC_BAD_ACCESS 错误的原因)。
从语法上讲,这两种形式都是正确的。如果您想更好地传达意图,请在想要直接使用指针时使用 self->varName,并在想要利用 @property 便捷方式时使用 self.varName

优秀的概述。您能否详细说明如果我们只是执行 varName = ...; 会出现什么问题? - mfaani

7
以下是可能的组合(我认为) 只有当`aString`属性具有`retain`属性时,OK和BAD才是正确的:
@property (nonatomic, retain) NSMutableString *aString;

So:

1

aString = [[NSMutableString alloc] init]; //OK: 

只有在aString没有指向无效对象的情况下,这样做才是可以的,否则你会失去对该对象的引用,导致内存泄漏,因为你无法释放它。

2

aString = [NSMutableString string]; //BAD

这段代码存在问题,因为你应该保留aString(因为你已经声明了它),但是你没有保留它,未来很可能会导致EXC_BAD_ACCESS错误。

3

aString = [[NSMutableString string] retain]; //OK

与第一种方法相同,只有在aString没有指向一个有效对象时才有效。不过我还是会选择第一种方法。
aString = [[[NSMutableString alloc] init] autorelease];//BAD

与第二种方法相同。

5

self.aString = [[NSMutableString alloc] init]; //BAD!!

这样不好,因为你重复保留了它,这会导致内存泄漏。

6

self.aString = [[NSMutableString string]; //******GOOD!******

这可能是最安全的方式。它将被属性设置器保留,并且由于您正在使用设置器,任何其他可能被aString指向的对象都将适当释放。

7

self.aString = [[NSMutableString string] retain]; //BAD

This is retained twice.

8

self.aString = [[[NSMutableString alloc] init] autorelease];//Ok

这也可以,但我更倾向于使用便捷方法而不是这种冗长的方式 :)

请注意,如果你知道你在做什么,#1和#3选项都是完全可行的。实际上,我比较频繁地使用它们而不是#6


感谢您的解决方案!我很感激您列出了不同的组合,这对我帮助很大!我通常最常使用#1,但我没有意识到6也可以。 - SpacePyro

2

除了在initdealloc和访问器本身中,始终使用访问器。这样做将为您节省许多头疼的问题,例如您正在描述的问题。此外,请将ivars命名为与属性不同的名称(_foofoo_mFoo,但不是foo)。

self.foo[self foo]完全相同。它调用方法fooself.foo = x[self setFoo:x]完全相同。它调用方法setFoo:。如果您将属性foo合成为一个retain变量,则它看起来像:

@synthesize foo = foo_;

- (void)setFoo:(id)value {
  [value retain];
  [foo_ release];
  foo_ = value;
}

这会正确释放foo_的旧值,分配一个新值并保留它。

foo = x(假设foo是实例变量)不会调用任何方法。没有。它只是将指向x的指针的值分配给foo中的指针。如果foo指向被保留的内容,则会泄漏该内容。如果您要赋的新值未被保留,那么您以后会崩溃。

解决方案是尽可能使用访问器。


非常感谢!总结得很好,讲得通透。我的Objective-C书籍/资源对此并没有多少介绍,所以我有些困惑。再次感谢! - SpacePyro

2
我个人更喜欢使用self.语法。这样更容易确定它是实例变量,而不仅仅是当前作用域中的其他变量,当其NSAutoreleasePool被释放时就会丢失。然而,两种方式都是正确的。如果你收到EXC_BAD_ACCESS错误,那并不是因为你没有使用self.访问它。你说的正确,必须分配它,无论你选择哪种方式访问变量,都要保持一致,否则你将收到错误提示。
希望这能帮到你。

1

二者皆可。

使用点语法更加简洁(对某些人来说),并且编译成等效的代码。例如,self.iVar 等同于 [self iVar]self.iVar = aValue 等同于 [self setIVar:aValue];


那不是他所问的,他在质疑是否应该完全省略self。 - Chance Hudson
1
不,self.iVar 基本上就是 [self iVar]iVar = aValue 是一种直接赋值。在直接赋值中不会发生 retaincopy - Deepak Danduprolu
2
@bioffe。KVC需要访问器使用-var来检索值和-setVar:来设置值。使用get的Cocoa方法名称用于通过引用返回值。例如,[NSColor](http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSColor_Class/Reference/Reference.html)中的`getComponents`。 - Abizern
@Abizern,我是在回应@bioffe的话。但现在想想,我可能也误读了他的话。对此感到抱歉。我应该标记一下我的回复对象。 - Deepak Danduprolu
@Deepak - 噢,我明白你的意思了。抱歉。 - Abizern
显示剩余3条评论

0

self.aString[self aString] 的语法糖。合成一个属性只会创建 -aString-setAString: 方法(取决于你选择的属性,它可能不是平凡的赋值)。

现在的问题是是否使用 . 符号。我建议你不要使用它。为什么?首先要知道 Objective-C 的目标只是作为 C 的补充。这意味着每个有效的 C 代码也是有效的 Objective-C 代码。

现在看看他们对点符号做了什么。最后一句话不再适用了。你无法区分访问 C 结构体字段和发送 Objective-C 方法。

所以请不要使用点符号。更喜欢使用 [self ..]。


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