一个 Cocoa Objective-C 类中变量前面的下划线是如何工作的?

159

我在一些 iPhone 的例子中看到过属性前面使用了下划线 _。有人知道这意味着什么吗?或者它是如何工作的?

我正在使用的接口文件看起来像:

@interface MissionCell : UITableViewCell {
    Mission *_mission;
    UILabel *_missionName;
}

@property (nonatomic, retain) UILabel *missionName;

- (Mission *)mission;

我不确定上面的代码具体是做什么的,但当我尝试设置任务名称时,使用以下代码:

aMission.missionName = missionName;

我遇到了以下错误:

在非结构体或联合体中请求成员“missionName”

9个回答

100

如果你为你的实例变量使用下划线前缀(这只是一个普遍的约定,但很有用),那么你需要做1件额外的事情,以便自动生成的访问器(用于属性)知道要使用哪个实例变量。具体来说,在你的实现文件中,synthesize 应该像这样:

@synthesize missionName = _missionName;

更普遍地说,这是:

@synthesize propertyName = _ivarName;

80
有了自动合成属性,这不再是必需的。Xcode在后台使用名为"_xxxx"的实例变量自动生成一个带有 @property xxxx 的代码。很方便。 - CodeSmile
@LearnCocos2D 你好!我是一个iOS的新手,有些事情需要澄清。这段时间以来,我一直在.h文件中声明property,然后在.m文件中使用self访问它,就像这样:self.someProperty。这是正确的方法吗?还是应该在代码中使用ivars? - Isuru
新手问题:为什么不能直接使用ivar?为什么我需要声明一个单独的变量来保存ivar? - Allen
1
@Allen,如果我理解你的问题正确: 你声明的独立变量是指向实际变量的指针。这有几个重要原因(据我所知)。 首先,当你将一个指针传递给函数时,你不会复制它的值。你只是告诉函数在哪里找到要使用的值。这有助于保持你使用的内存低(也有助于分配和释放内存,在Java中没有"垃圾回收"机制,这很重要)。 - David Sigley
还有一些很棒的测试可以运行,以检查直接访问时更改值的完整性。(由于副本的制作,通常在操作它时您的原始变量不会更改) - David Sigley
显示剩余2条评论

19

这只是为了可读性而约定的,它并没有对编译器产生任何特殊作用。你会看到人们在私有实例变量和方法名称上使用它。事实上,苹果公司建议不要使用下划线(如果你不小心的话可能会覆盖超类中的某些内容),但你不应该因此感到不安。


19
据我所了解,Apple不建议在方法名称前使用下划线前缀(这是他们作为私有方法的约定),但他们没有类似的建议适用于实例变量名称。 - Kelan
9
实际上,苹果公司鼓励这样做:“通常情况下,您不应直接访问实例变量,而应使用访问器方法(在init和dealloc方法中可以直接访问实例变量)。为了帮助表明这一点,将实例变量名前缀加上下划线(_),例如:@implementation MyClass { BOOL _showsTitle; }”。 - dmirkitanov
我实际上不认为苹果鼓励我们这样做,因为他们在iOS开发者文库中的所有示例代码都没有使用下划线。苹果还表示他们已经保留了它,这必须意味着他们在内部使用它来构建自己的框架,如UIKit等。这就是为什么我们不应该随意使用它的原因。但是我看到你提供的链接@kelan中,他们实际上在“修订历史”中说可以使用下划线。我理解为如果我们想要使用它,我们“可以”使用它。 - WYS
苹果公司的文档指出不要在方法名前使用下划线前缀,文档链接在这里:https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/NamingMethods.html#//apple_ref/doc/uid/20001282-1003829-BCIBDJCA。 - ThomasW

9
我所见到的唯一有用的目的是区分局部变量和成员变量,但这不是必要的惯例。与@property配对使用时,它会增加synthesize语句的冗长性 - @synthesize missionName = _missionName;,而且在任何地方都很丑陋。
相反,只需在方法中使用具有描述性的变量名称,避免冲突即可。当它们必须冲突时,方法内的变量名应该加一个下划线,而不是可能被多个方法使用的成员变量。唯一有用的常见情况是在setter或init方法中。此外,它将使@synthesize语句更简洁。
-(void)setMyString:(NSString*)_myString
{
    myString = _myString;
}

编辑: 使用最新的编译器自动合成功能,现在我使用下划线来表示实例变量(在极少数情况下需要使用实例变量来匹配自动合成的操作)。


这是另一种方式。私有变量有下划线,而属性没有。当合成它们时,你将它们耦合在一起。 - Justin
这正如我所描述的那样,只是我称其为“成员变量”,而不是“私有变量”。 - Peter DeWeese
哎呀!这会带来麻烦...自动合成将创建一个名为_myString的实例变量,这意味着您的setter方法将无法正常工作(因为它无法区分实例变量和方法参数)。 - geowar
没错,这就是为什么当苹果添加自动合成时,我在结尾处添加了编辑。 - Peter DeWeese

5

这并没有太多实际意义,只是一些人用来区分成员变量和局部变量的约定。

至于错误,听起来aMission的类型可能有误。它的声明是什么?


这在带有智能感知的集成开发环境中很常见;它将使您的成员/模块/类变量显示在列表顶部。另一个常见的前缀是“ m_”。 - STW
1
如果它没有任何意义,你怎么能像上面的例子中那样在_missionName和missionName之间来回切换呢?我的声明看起来像这样:Mission *aMission = [[Mission alloc] init]; aMission.missionName = @"一个任务"; - Atma
1
一个是实例变量,另一个是属性。你不能使用像 aMission.missionName 这样的语法访问实例变量,因为这种语法在指针中不起作用。 - Chuck
此外,请注意您正尝试操作Mission对象,但您发布的接口具有missionName属性的MissionCell。 - smorgan

2

这只是关于synthesize属性命名规则的内容。

当您在.m文件中合成变量时,Xcode将自动为您提供_variable智能提示。


1
这似乎是关于 self.variableName 和 _variablename 的问题的“主”项。 使我困惑的是,在 .h 文件中,我写了以下内容:
...
@interface myClass : parentClass {
className *variableName;    // Note lack of _
}

@property (strong, nonatomic) className  *variableName;
...

这导致self.variableName和_variableName在.m文件中成为两个不同的变量。我需要的是:
...
@interface myClass : parentClass {
className *_variableName;    // Note presence of _
}

@property (strong, nonatomic) className  *variableName;
...

然后,在类的 .m 文件中,self.variableName 和 _variableName 是等效的。

我仍然不清楚的是,为什么许多示例仍然有效,即使没有这样做。

Ray


1
拥有下划线不仅可以在不使用self.member语法的情况下解析你的ivar,还使得你的代码更易读,因为你知道一个变量是ivar(因为它有下划线前缀)还是成员参数(没有下划线)。
示例:
- (void) displayImage: (UIImage *) image {

    if (image != nil) {
        // Display the passed image...
        [_imageView setImage: image];
    } else {
        // fall back on the default image...
        [_imageView setImage: _image];
    }
}

在这个例子中,看到使用self.image(或[self image])的比较会很好。什么时候最好使用self.image,什么时候最好使用_image? - Boeckm
2
通常情况下,您应该使用self.image来访问属性。唯一应该直接访问实例变量_image的时候是在init方法和dealloc方法中,因为调用任何其他方法可能会有风险(因为对象只是半初始化或半释放)。 - Peter Hosey

0
其他答案中缺少的是使用_variable可防止您不经意地键入variable并访问ivar而不是(预期的)属性。

编译器将强制您使用self.variable_variable。使用下划线使得无法键入variable,从而减少程序员错误。

- (void)fooMethod {

    // ERROR - "Use of undeclared identifier 'foo', did you mean '_foo'?"
    foo = @1;

    // So instead you must specifically choose to use the property or the ivar:

    // Property
    self.foo = @1;

    // Ivar
    _foo = @1;

}

0

你可以使用self.variable name代替下划线,或者合成变量以在不使用下划线的情况下使用变量或输出口。


2
如果您只需要在同一类中使用变量,只需在.m文件中声明它即可,这样可以允许您在调用时不需要使用self或下划线。 - Ansal Antony

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