Objective C中的实例变量/方法参数命名

15
这里有关于命名实例变量和方法参数的惯例,特别是当方法参数用于设置实例变量时,请问在这里的人们都遵循什么惯例呢?
在C++中,我经常使用前缀m_来表示实例变量。在C#中,我按照使用this.来区分实例变量的惯例来命名。后来我也在C++中采用了同样的方式(this->)。
在Objective C中,我尝试了一些东西,但没有一个真正令人满意的。
除非有人提出真正好的建议,否则我不得不妥协(但请不要让我使用the前缀来表示参数!),所以我很想听听大多数人的意见-特别是那些长时间使用ObjC的人。
在发布此帖子之前,我进行了一些尽职调查,并发现了几个不错的资源: 它们给了我一些想法,但我仍然很想听听其他人的做法。
[编辑] 只是为了澄清:我特别想知道如何区分实例变量和方法参数-无论是通过前缀还是其他技术。
[编辑2] 感谢所有回答和讨论点。我不会关闭此贴,但仅想说,正如我在接受的答案的评论中所指出的那样,我使用加前缀the来表示初始化参数(并使用new来表示setter参数,这是我以前就在做的)。尽管我自己对这种美学不太热衷,但这似乎是权衡各方面的最佳选择。
5个回答

17

你已经注意到了,Cocoa风格是在方法参数名称与实例变量冲突时使用theValue这样的名称。然而,在Objective-C 2.0风格的代码中,这种情况应该不会发生太多次。假设你通常不会直接访问实例变量。主要原因是这样做会绕过Cocoa中的键值观察机制。相反,预期是通过getter/setter方法来访问和修改属性。在Objective-C 2.0中,可以很容易地声明这些属性并自动合成getter/setter,因此没有太多理由不使用它们。事实上,在64位系统上,运行时将自动为您创建实例变量,从而避免了需要声明它们并减少了使用它们的冲动。

唯一可以直接访问实例变量的时间是在-init-dealloc方法中:

@interface MyObject : NSObject 
{
  id ivar;
}

@property (retain,readwrite) id ivar; //or whatever retain/copy/assign and read/readwrite makes sense
@end

@implementation MyObject
@synthesize ivar;

- (id)initWithIvar:(id)theIvar {
  if(self = [super init]) {
    ivar = theIvar;
  }

  return self;
}

- (void)dealloc {
  [ivar release];
}
在这些情况下应该直接使用 ivar 的原因是 getter/setter 可能会有依赖于完全初始化实例的副作用,因此在 -init 和 -dealloc 中使用它们是危险的,因为对象的状态已经完全初始化。在所有其他情况下,您应该使用 self.ivar(或 [self ivar] 和 [self setIvar:newValue])。
似乎除了 -initWithXX 之外的方法不应该出现名称冲突。如果确实存在,那么它们是否应该被重构以不具有该参数或使类不具有实例变量?
这将仅留下 -initWithXX 方法,在这种情况下,参数和 ivars 之间经常会发生冲突。对于这种情况,如果您真的无法忍受 Cocoa 风格,则可以使用您提到的任何方法。前缀使用 _ 是有效的且相对常见的(我相信 @synthesize 的 setter 和 getter 在这种情况下会自动执行正确的操作,但您可能需要显式设置 _ivar 作为支撑)。

2
谢谢Barry。在类实现中使用访问器的问题是它们会增加显著的开销 - 在桌面应用程序中您可能可以忽略它们,但在iPhone上它们可能会累积。此外,正如您所说,它们可能具有您不希望从impl内部发生的副作用... - philsquared
1
话虽如此,大多数冲突发生在init方法中(在mutator中,我通常会使用newX或类似的名称)。由于共识似乎是将or a作为参数的前缀,因此我可以在init方法中接受这种方式。这样可以使实例变量更清晰。 - philsquared
1
我想要验证属性访问与直接 ivar 访问的性能损失是不可接受的。我在上面澄清了为什么你必须在 init/dealloc 中直接访问 ivar。否则,直接访问和属性访问共存似乎很危险,因为它会混淆 ivar 状态的语义。 - Barry Wark
1
直接访问由实现方提供,而属性访问则由外部用户提供,这在大多数语言中已经成为了一种惯用法——尽管我承认有充分的理由使所有访问都默认通过访问器进行。然而,我还没有完全接受这种做法。 - philsquared
1
在dealloc中应该避免使用属性的另一个原因是,我认为这比设置器可能依赖于此处无效状态的事实更具有说服力。设置属性将触发KVO通知,在-dealloc中绝对要避免这种情况。 - Lily Ballard
显示剩余8条评论

2
为了符合所有已知的 Objective-C 代码规范,可以参考谷歌的版本这里。他们的做法是在成员变量名后面添加下划线,例如BOOL isActive_;。所以要做出选择并坚持下去。我也更喜欢在我的应用程序中使用下划线前缀。

1

感谢wfarr。然而,这并没有描述实例变量或方法参数的命名约定(除了说,“确保实例变量的名称简洁地描述存储的属性。”) - philsquared
你的编辑澄清了事情。我相信我已经看到人们只是使用_作为前缀来区分ivars和方法参数。 - wfarr
使用“_”作为前缀的问题在于它被苹果公司保留,并且他们在一些基础类中使用它(有关此信息的详细内容可参见我给出的某些链接)。 - philsquared
3
苹果公司并没有将下划线前缀保留给实例变量名,仅用于方法名。 - Chris Hanson
ANSI C保留所有以_开头的标识符供自己、编译器和/或操作系统使用。如果您使用它,苹果可能不会使您的代码无效,但下一个C标准可能会这样做。或者(小概率)下一个编译器可能会这样做。 - Stripes
1
在C语言中,只有以大写字母开头的“_”是保留的(双下划线也是保留的)。而下划线后面跟着小写字母则是可以使用的。 - drewag

1

Obj-C并没有像许多其他语言那样严格定义“风格”,这可能是好事,也可能是坏事,但这意味着大部分时间你需要自己找到一个好的编码风格。

你也可以通过self访问Obj-C中的变量。因此,如果你有一个实例变量test,你可以通过self->test来访问它,这是合法的,并且总是有效的。虽然在大多数Obj-C程序员眼中并不美观。它泄露了“秘密”,即对象实际上只是结构体(更准确地说,对象引用是指向结构体的指针),而实例变量实际上是结构体成员。虽然这并不是真正的秘密,但Obj-C程序员似乎更喜欢在他们的代码中“隐藏”这个事实。

在名称中使用下划线“_”是一个非常糟糕的主意。这里有人指出Apple保留下划线用于他们的代码,但实际上GCC已经为符号名称保留了下划线。更准确地说,ANSI-C标准已经规定以两个下划线或一个下划线和一个大写字母开头的变量保留给编译器内部使用。因此,在理论上使用一个下划线是有效的,但如果不小心以大写字母开头,则变得无效。

到目前为止,我尝试过使用前缀my,例如myName代替name,以及使用前缀self,例如selfName代替name;一开始看起来都有些奇怪,但在大段代码中并不太糟糕。至少它立即引起了注意,因为它是“不同的”。我还尝试了一个单独的“i”,iName代替name(或者iname代替name),但我对这个解决方案并不满意。
虽然如此,我从未浪费时间思考方法参数。因为它并不重要。它们就像任何其他变量一样,除非它们被声明为常量。它们甚至可以在方法内部重新用于其他目的,因为这不会影响调用者。
- (NSImage *)loadImage:(int)imageNumber
{
  NSImage * res;

  // Some code that assigns a value to res
  // ...  

  // Re-use imageNumber for a different purpose
  for (imageNumber = 0; ...; ...) {
     // Some code
  }

  return res;
}

我认为这段代码没有问题。只要名称仍然有意义,为什么我必须声明第二个堆栈变量呢(如果我在 for 循环中不按图像编号迭代,则名称当然没有意义,在这种情况下,我会使用不同的变量 - 编译器实际上可能只为两者之一保留一个 int 堆栈变量)。


1
通常,Apple 的示例代码使用“_”前缀。我认为我也看到一些人使用mFoom_foo。有些人根本不使用前缀,而只是使用普通名称,但这会在以后导致混乱。通常,在定义方法参数时,命名约定是使用“a”、“an”、“the”或“new”前缀。例如:
@interface Foo : NSObject {
    id _bar;
}
@property (nonatomic, retain) id bar;

- (id) initWithBar:(id)aBar;

@end

@implementation Foo
@synthesize bar = _bar;

- (id) initWithBar:(id)aBar {
    self = [super init];
    if(self != nil) {
        _bar = aBar;
    }
    return self;
}

@end

我发现这种约定非常有效。过去我并不烦恼前缀,但有时候会让事情变得混乱。使用前缀清晰地表明这是一个实例变量。@synthesize bar = _bar 约定被苹果公司在他们的(iPhone)示例代码中使用。

实例变量通常也不会被使用,所以如果你觉得“_”前缀很烦人,那没关系,因为你会使用[self bar](或者如果你喜欢,self.bar)。


1
感谢sebnow。苹果公司为避免名称冲突而保留了下划线前缀(参见:http://developer.apple.com/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/NamingBasics.html#//apple_ref/doc/uid/20001281-和“印刷约定”的结尾)。 我更愿意避免使用它。 - philsquared
3
苹果公司并没有保留下划线前缀作为实例变量名,只用于方法名。 - Chris Hanson
@Chris:在那篇文章中提到,“避免使用下划线作为私有前缀,特别是在方法中。苹果公司保留了这种约定的使用权。”请注意特别是在方法中,而不是仅限于方法... - philsquared
我理解(在上下文中)的意思是,“这是针对方法名称保留的,但也是一个指南,不要在其他名称中使用它”。所以我并不是说你错了 - 我撤回了使用“保留”一词的用法 - 但它仍然警告不要使用它。 - philsquared
2
ISO C标准规定,所有以下划线开头并后跟大写字母的"符号"(符号可以代表任何东西,如变量、函数等)都保留给编译器内部使用。这意味着,如果您的ivars以下划线开头,并将该下划线后的第一个字母大写,则会违反C标准。不要在符号名称开头使用下划线,这几乎从来不是一个好主意。 - Mecki

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