类属性mVar和实例变量self.mVar的区别

4

我有点困惑在类内部访问实例变量时,使用self和直接使用变量名的区别。

例如,看看这个类:

//In .h file:
@interface Register : NSObject {
    NSString *mName;
}

- (id) initWithName:(NSString *) name;

//In .m file:
- (id) initWithName:(NSString *)name
{
    if (self == [super init])
    {
        mName = name;
    }
    return self;
}

什么是通过以下方式访问实例变量的区别:
self.mName = name;

vs

mName = name;

这不是@property,也没有被@sythenize。

举个例子:

//In .h file:
@interface Manange_My_ViewsViewController : UIViewController { 
    IBOutlet UILabel *countLabel;
}

@property (nonatomic, retain) IBOutlet UILabel *countLabel;

//In .m file:
@synthesize countLabel;

- (void) updateLabel:(NSUInteger)count
{
    countLabel.text = [NSString stringWithFormat:@"%d", count];
}

但是如果我这样访问countLabel:

self.countLabel

有什么区别呢?

编辑:根据用户的回答,第三个例子:

如果iVar不是IBOutlet,那会有什么不同呢?

//In .h file:
@interface Fake : NSObject {
    NSString *mVar;
}
@property (nonatomic, retain) NSString *mVar;

//In .m file:
 @synthesize mVar;

 mVar = @"";

VS

 self.mVar = @"";

那么这两种方式是相同的吗?第一种方式我们正在访问实际的实例变量,而第二种方式我们通过 @synthesize 自动生成的 setter 进行访问?

谢谢大家!

编辑:响应 Peter Hosey 的更新...

所以你认为 mVarName 的命名约定不好吗?我从 C++ 时代学来的。

但如果你这样做呢?

-(void) someMethod:(int) x
{
    x = x;
}

你不能这样做(说“x”也是一个类变量)

但是你可以这样做:

-(void) someMethod:(int) x
{
    mX = x;
}

但是您的意思是最好这样做:
-(void) someMethod:(int) x
{
    self.x = x;
}

“但你说最好这样做:…” 不,我没有说过那个。我已经添加了一个部分来解决这个问题。 - Peter Hosey
1
谢谢大家的回复,我把这个问题打印出来,在地铁上反复阅读直到理解。然后我试着向别人解释它。嘿,谢谢! - mr-sk
3个回答

7

What's the difference between accessing the instance variable via

self.mName = name;

vs

mName = name;

首先是属性访问语法,它会将访问器消息翻译为对象(在此例中,是self)的访问器消息。也就是说,该语句隐含地将被翻译为这个消息表达式语句:

[self setMName:name];

尴尬的访问器名称就是为什么“mName”不适合作为属性名称的原因。有一种属性声明语法可以解决这个问题,让你将属性命名为“name”,将实例变量命名为“mName”,并将一个映射到另一个。
第二个例子直接访问实例变量 - 没有访问器消息。
如果没有为类声明名为“mName”的属性,则无法使用属性访问语法在该类的实例上访问该名称的属性。
而且无论您是合成访问器,将它们挥手给超类与@dynamic,还是自己定义它们,都没有关系。对象将以这种方式响应访问器消息,但编译器生成的访问器消息不会有任何区别(因为属性访问同样可能来自类内部和外部)。
事实上,IBOutlet目前只是扩展为空的宏。在您的代码被预处理后,单词“IBOutlet”不再存在,因此编译器永远看不到它。除了IB之外,它对任何东西都没有什么影响:完全没有。

根据问题编辑进行编辑

我说mName作为属性名称很糟糕,因为它随之而来的访问器名称。实例变量的名称是一个单独的问题,特别是因为属性和实例变量不必具有相同的名称。
对于变量,无论是实例变量还是局部变量,选择namem_namemName纯粹是一种风格选择。 someMethod:通常是访问器,setX:。在该方法内,self.x = x,即[self setX:x],会导致无限递归。所以不要这样做。
someMethod:不是访问器(或initdealloc)时,在该情况下使用属性是非常好的,而且通常更可取。但在这种情况下,您不太可能给其参数命名与实例变量相同。当这种情况出现时,将本地变量命名得更加具体,因为它的目的更加具体。这也是一个风格问题。

当它是访问器时,我会将本地变量命名为newX,并将实例变量与属性名称相同,即x。这是我的个人风格;正如我所说,将属性命名为x,ivar命名为mX,局部变量命名为x也可以(除了这个示例过于简短之外)。


什么?你不能在“id”变量上进行属性访问 - 编译器会出现类似“错误:请求在非结构体或联合体中的成员'count'”的错误。 - Chuck
不好的例子(我不认为Cocoa中有一个名为 count 的属性,所以至少发出警告是完全合理的),但你是对的 - 我刚刚使用一个已知存在的属性尝试了一下,并得到了那个结果。现在我想我记得bbum之前发布过一个原因。感谢您对检查该声明的敦促;我将从我的答案中将其删除。 - Peter Hosey

3

好的,首先是基本区别:

mVar = var;

这只是改变一个值。就这样。

self.mVar = var;

这等同于:
[self setMVar:var];

换句话说,一个调用了方法,而另一个没有。使用@property语法可以带来一些非常好的好处。例如,您可以免费获得键值编码兼容性。这意味着另一个对象可以观察此对象的mVar属性,并在其更改时自动通知,而无需您做任何操作。如果您直接访问实例变量,则无法获得此功能。(当然,除非您自己实现它。但是为什么要这样做呢?)
您还可以获得半自由的内存管理。如果将属性声明为(retain),则无需自行执行[newValue retain]。合成方法会为您执行此操作(在两种情况下,您仍需要在dealloc方法中执行[ivar release])。
你也可以获得一定程度的线程安全。如果你不将属性声明为(nonatomic),那么它就是(默认) atomic(尽管该关键字不存在;它是隐含的)。这意味着读取/更新属性的值是一个原子操作。如果你直接访问ivar,你必须使用锁自己实现原子性。
基本上,使用合成方法会免费为你提供一些非常棒的东西。我唯一建议使用@property语法的原因是如果你有确凿的证据表明调用这些方法是你代码中的瓶颈。然而,你将非常难以想出这种情况。

1
首先,对于只读属性——IBOutlet 的实质——它并不重要。
最关键的区别是:调用 property 实际上是调用了访问器方法,而直接访问实例变量是直接访问实例变量本身。
因此,对于设置保留属性,使用 self 和访问器将释放旧对象并保留新对象。直接设置实例变量不会影响任何对象的保留计数。
使用 @synthesize 将为您生成标准访问器。
使用属性的主要原因是,由于它们是访问器,因此可以从类外部读取和/或修改它们。

好的-明白了。如果那不是IBOutlet呢?(在我的问题中添加第三个示例) - mr-sk
“使用属性的主要原因是,由于它们是访问器,可以从类外部读取和/或修改它们。” - 好的,但在类内部呢?通过self.iVar访问它们是否更好,还是直接使用iVar?什么被认为是最佳实践? - mr-sk
我认为这取决于情况。但要记住它们之间的区别。如果您正在分配和初始化一个新对象,可以直接将其分配给实例变量,但对于保留属性,您必须释放该对象,否则会造成内存泄漏。 - gerry3
1
即使使用retain属性,您仍然需要在dealloc方法中释放它。 - Dave DeLong
@Dave,但我的意思是你必须立即释放它,因为它是从alloc返回的,其保留计数为1,而你通过设置属性来增加它。当然,如果你使用了方便的构造函数并获取了一个自动释放的对象,则不是这种情况。 - gerry3

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