声明属性的点表示法与消息表示法

80
我们现在有了属性的“点”表示法。我见过关于使用点表示法与使用消息表示法的优点的各种讨论争论。为了保持回答的客观性,我不会在问题中偏向任何一方。
你对使用点表示法与使用消息表示法来访问属性有什么想法?
请尽量集中讨论Objective-C - 我的一个偏见是Objective-C就是Objective-C,所以你认为它应该像Java或JavaScript那样的偏好是无效的。
有效的评论与技术问题(操作顺序、转换优先级、性能等)、清晰度(结构与对象性质,利弊双方!)、简洁性等有关。
请注意,我是严格追求代码质量和可读性的学派,在这些巨大的项目中工作过,代码规范和质量至关重要(一次编写,千万次阅读的范例)。

1
源代码编辑器应该根据程序员的个人偏好进行调整,而不改变文件本身的字符。这样每个人都能得到自己喜欢的东西。 - Arne Evertsson
19个回答

64

不要使用点来表示行为(behavior)。使用点来访问或设置像“stuff”这样的属性,通常是声明为属性的属性。

x = foo.name; // good
foo.age = 42; // good

y = x.retain; // bad

k.release; // compiler should warn, but some don't. Oops.

v.lockFocusIfCanDraw; /// ooh... no. bad bad bad

对于刚接触Objective-C的人,我建议不要在除了被声明为@property的东西以外的地方使用点语法。一旦您对这门语言有所了解,就做自己感觉正确的事情。

例如,我认为以下写法很自然:

k = anArray.count;
for (NSView *v in myView.subviews) { ... };

您可以期望Clang静态分析器将增强其功能,使您能够检查点是否仅用于某些模式或不用于其他某些模式。


点符号表示法仅用于访问器方法。这意味着它可以作为 [foo getBar] 或 [foo setBar:bar] 的替代方式使用;但不能用于通用方法。 - apaderno
13
那是不正确的。点表示法只是一个等效方法调用的简单同义词,没有更多,也没有更少。 - bbum
2
我想知道为什么点表示法没有被限制在@property声明的访问器中。看起来这样做可以让类的作者解决哪些方法应该允许在点表示法中的灰色地带,而且如果允许类表达其意图并由编译器强制执行,那么是否使用.uppercaseString之类的方法就不会感到模糊了。也许我还缺少一些东西,需要当前点表示法的实现。 - user155959
4
它本来可以仅限于@property,但这将要求API的使用者学习另一个布尔信息维度,并且API的设计者很可能会在应该和不应该成为@property的参数上陷入争论。请注意,Xcode更喜欢——更加重视——在完成代码时隐含的@property方法。这是鼓励的,但并非强制性的。 - bbum
虽然这是优秀的编码实践,但我很困惑为什么这个得票最高的答案没有回答原始发布的问题,即我们应该使用 myObj.prop 还是 [myObj prop] 进行编码? - rodamn
1
@rodamn,它确实回答了这一点; 当接触 Objective-C 语言时,建议仅在使用声明为 @property 的访问器时使用圆点。随着对Objective-C编码的信心增强,可以根据自己的喜好进行选择。有趣的是,我们中的许多人已经开始非常有限地使用圆点。 - bbum

22

首先,我想说我以前使用过Visual/Real Basic编程语言,然后转向了Java,所以我非常习惯点语法。但是,当我最终转向Objective-C并习惯了方括号之后,看到Objective-C 2.0及其点语法的介绍时,我意识到我真的不喜欢它(对于其他语言来说,这是可以接受的,因为它们的用法如此)。

我对Objective-C的点语法有三个主要的抱怨:

抱怨#1: 它使错误原因不清楚。例如,如果我有以下代码:

something.frame.origin.x = 42;

那么我会得到一个编译器错误,因为something是一个对象,而不能将结构体用作表达式的左值。但是,如果我有:

something.frame.origin.x = 42;

那么它将完美地编译通过,因为something本身就是一个结构体,它有一个NSRect成员,并且我可以将其用作左值。

如果我要采用这段代码,我需要花一些时间来弄清楚something是什么。它是一个结构体吗?还是一个对象?然而,当我们使用方括号语法时,这就清楚多了:

[something setFrame:newFrame];

在这种情况下,如果something是一个对象还是其他,就没有任何歧义。引入歧义是我的第一个抱怨。

争议2:在C语言中,点语法用于访问结构体成员,而非调用方法。程序员可以重写对象的setFoo:foo方法,但仍然可以通过something.foo访问它们。当我看到使用点语法的表达式时,我期望它们是简单地分配给ivar,但这并不总是正确的。考虑一个控制器对象,它协调数组和表视图。如果我调用myController.contentArray = newArray;,我希望它将旧数组替换为新数组。但是,原始程序员可能已经覆盖了setContentArray:,不仅设置数组,还重新加载表视图。从代码行中,无法得知该行为。如果我看到[myController setContentArray:newArray];,那么我会想:“啊哈,一个方法。我需要去查看此方法的定义,以确保我知道它正在做什么。”

所以我认为我对争议2的总结是:您可以使用自定义代码来覆盖点语法的含义。

争议3:我认为它看起来很糟糕。作为一个Objective-C程序员,我完全习惯括号语法,因此阅读美丽的括号行和行的代码,然后突然被foo.name = newName; foo.size = newSize;等打断有点分散我的注意力。我意识到有些东西需要使用点语法(C结构体),但那是我使用它们的唯一时间。

当然,如果您正在为自己编写代码,则可以使用任何您感到舒适的内容。但是,如果您正在编写计划开源的代码,或者您正在编写不希望永远维护的代码,则强烈建议使用括号语法。当然,这只是我的意见。

反对点语法的博客文章:https://bignerdranch.com/blog/dot-notation-syntax/

对上述文章的反驳:http://eschatologist.net/blog/?p=226(支持点语法的原始文章:http://eschatologist.net/blog/?p=160


5
对我来说,牛肉#1似乎有些站不住脚。我不明白为什么这比您试图向结构发送消息,然后必须弄清楚变量实际上是结构体更加关键。您是否真的看到很多了解其工作原理的人在实践中感到困惑?这似乎是那种你会犯错一次,学习lvalue的事情,然后将来做正确的事情的情况 - 就像不试图将NSNumbers视为ints一样。 - Chuck
2
@Chuck 这有点边缘情况,但我做了很多合同开发,其中涉及继承项目并需要与它们一起工作。通常 something 是一个对象,但我遇到过几个地方,原作者创建了结构体(为了速度、更低的内存使用等),我不得不花费一些时间来弄清楚为什么代码无法编译。 - Dave DeLong

14

我是一名新的Cocoa/Objective-C开发者,我的看法是:

我坚持使用消息传递符号,尽管我从Obj-C 2.0开始学习,即使点符号更加熟悉(Java是我的第一种语言)。我这么做的原因很简单:我仍然不完全明白为什么要将点符号添加到该语言中。对我来说,它似乎是一个不必要、"不纯"的添加。但是如果有人能解释它如何有益于该语言,我很乐意听取。

然而,我认为这是一种风格选择,我认为没有对错之分,只要保持一致和可读性,就像任何其他风格选择一样(比如把大括号放在方法头的同一行或下一行)。


2
+1 个好答案。我真的很想知道背后的“为什么”。也许 bbum 或 mmalc 知道答案...(他们都在工作) - Dave DeLong

11

Objective-C点表示法是一种语法糖,被翻译为普通的消息传递,在底层不会有任何变化,对运行时也没有影响。点表示法绝对不比消息传递更快。

经过这个必要的开场白,以下是我看到的优缺点:

点表示法的优缺点

  • 优点

    • 可读性:点表示法比嵌套的括号消息传递更容易阅读。
    • 它简化了与属性和属性交互:使用点表示法处理属性和使用消息表示法处理方法,可以在语法层面上实现状态和行为的分离
    • 可以使用复合赋值运算符(1)
    • 使用@property和点表示法,编译器可以为你生成良好的内存管理代码当获取和设置属性时;这就是为什么点表示法被Apple自己的官方指南建议使用的原因。
  • 缺点

    • 点表示法仅允许访问已声明的@property
    • 由于Objective-C是标准C的一层(语言扩展),点表示法并不真正清楚访问的实体是对象还是结构体。通常看起来像是在访问结构体属性。
    • 用点符号调用方法失去了命名参数的可读性优势。
    • 当混合使用消息表示法和点表示法时,好像你正在使用两种不同的语言编码。

代码示例:

(1)复合操作符使用示例代码:

//Use of compound operator on a property of an object
anObject.var += 1;
//This is not possible with standard message notation
[anObject setVar:[anObject var] + 1];

7
使用与语言本身一致的语言风格是最好的建议。然而,这并不是在OO系统中编写功能代码(或反之亦然)的情况,点符号是Objective-C 2.0语法的一部分。
任何系统都可能被误用。所有基于C的语言中都存在预处理器,足以做出非常奇怪的事情;如果您需要看到确切的奇怪情况,请看Obfuscated C Contest。这是否意味着预处理器自动是坏的,你永远不应该使用它?
对已在接口中定义为属性的属性使用点语法访问可能会被滥用。潜在的滥用存在不一定是反对它的论据。

属性访问可能具有副作用。这与获取该属性使用的语法是正交的。CoreData、委托、动态属性(first+last=full)都必须在底层执行一些工作。但是这将会混淆“实例变量”和对象的“属性”。特别地,属性不一定需要被存储为原样,特别是如果它们可以被计算(例如字符串的长度)。因此,无论你使用foo.fullName还是[foo fullName],仍然会进行动态评估。

最后,属性的行为(当作为一个lvalue使用时)由对象本身定义,比如是否复制或保留。这使得更改行为变得更容易 - 在属性定义本身中 - 而不必重新实现方法。这增加了方法的灵活性,减少了发生错误的可能性。仍然有选择错误方法的可能性(即复制而不是保留),但这是一个架构问题而不是实现问题。

最终,归结为“它看起来像结构体吗”的问题。这可能是迄今为止辩论中的主要区别;如果你有一个结构体,它的工作方式与如果你有一个对象不同。但这一直是真的;你不能给结构体发送消息,并且你需要知道它是基于堆栈还是基于引用/分配的内存。已经存在在使用上存在差异的心理模型([[CGRect alloc] init]或struct CGRect?)。它们从行为上来说从未统一过;你需要在每种情况下知道你正在处理什么。为对象添加属性表示极不可能混淆任何知道他们数据类型的程序员;如果他们不知道,那么问题更大。

至于一致性;(Objective-C)C本身是不一致的。=同时用于赋值和相等,基于源代码中的词汇位置。*用于指针和乘法。BOOL是char,而不是字节(或其他整数值),尽管YES和NO分别是1和0。一致性或纯洁性并不是这门语言的设计目标;它是关于完成任务。

所以如果你不想使用它,就不要使用它。用另一种方式完成任务。如果你想使用它,并且理解它,那么使用它是可以的。其他语言处理通用数据结构(映射/结构)和对象类型(具有属性),通常使用相同的语法,尽管其中一个仅仅是数据结构,而另一个则是一个丰富的对象。Objective-C程序员应该有能力处理所有编程风格,即使这不是你首选的编程风格。

6

我使用它来表示属性,因为

for ( Person *person in group.people){ ... }

比起另一种方式,这种方式更易于阅读。
for ( Person *person in [group  people]){ ... }

在第二种情况下,将大脑置于消息发送模式会影响可读性,而在第一种情况下,很明显你正在访问组对象的people属性。
例如,在修改集合时,我也会使用它:
[group.people addObject:another_person];

比起

,这个更易读。

[[group people] addObject:another_person];

在这种情况下,重点应该放在将对象添加到数组中的操作上,而不是链接两个消息。

6
我大部分时间都是在Objective-C 2.0时代长大的,我更喜欢点语法。对我来说,它可以简化代码,而不是额外添加括号,我只需使用一个点即可。
我也喜欢点语法,因为它让我真正感觉到我正在访问对象的属性,而不仅仅是发送消息(当然,点语法确实转换为消息发送,但从外观上来看,点感觉不同)。与旧语法中“调用getter”不同,它真的让我感觉像是直接从对象中获取了有用的东西。
这方面的一些争论涉及“但我们已经有了点语法,它是用于结构体的!”。这是真的。但是(再次强调,这只是心理上的)它基本上对我来说感觉相同。使用点语法访问对象的属性与访问结构体成员几乎是预期的效果(在我看来)。
****编辑:正如bbum指出的那样,您还可以使用点语法调用对象上的任何方法(我不知道这一点)。因此,我会说我的点语法观点仅适用于处理对象的属性,而不是日常消息发送。

3

我更喜欢消息语法...但只是因为这是我学习的方式。考虑到我的很多课程和其他内容都是以Objective-C 1.0风格为主,我不想混合使用。除了“我习惯了”之外,我没有真正的理由不使用点语法...除了这个问题,这让我疯狂。

[myInstance.methodThatReturnsAnObject sendAMessageToIt]

我不知道为什么,但这让我非常愤怒,没有任何好的理由。我只是认为做这件事情太麻烦了。
[[myInstance methodThatReturnsAnObject] sendAMessageToIt]

更可读。但每个人都有自己的喜好!


3
我偶尔会这样做,但只有在访问属性时才这样做。例如: [self.title lowercaseString] 或其他类似情况。但我可以理解为什么在风格上混合使用语法会让人感到困扰。我之所以这样做是为了清楚地区分什么是属性,什么是返回对象的方法(我知道属性本质上就是这样的,但你应该明白我的意思)。 - jbrennan

3
面向对象编程的主要优点之一是没有直接访问对象内部状态的方式。
点语法似乎是试图使其看起来和感觉像是直接访问状态。但事实上,它只是行为 -foo 和 -setFoo: 的语法糖。我个人更喜欢说清楚。点语法有助于可读性,因为代码更加简洁,但它并不帮助理解,因为忘记你实际上是在调用-foo和-setFoo:可能会导致麻烦。
合成访问器似乎是试图使编写直接访问状态的对象变得容易。我认为这鼓励了面向对象编程旨在避免的程序设计类型。
总的来说,我宁愿没有引入点语法和属性。我曾经能够告诉人们ObjC是几个干净的扩展,使其更像Smalltalk,但我不再认为这是真的了。

3
老实说,我认为这归结于风格问题。就我个人而言,我不赞成点语法(尤其是在刚刚发现你可以用它来调用方法而不仅仅是读/写变量之后)。但是,如果你要使用它,我强烈建议不要将其用于除访问和更改变量之外的任何内容。

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