2

假设我有一个简单的类,如下所示:

@interface A { 
// @public
    int var;
}
// @property(some_property) int var;
@end

当我想访问变量 var 时,我有一些选择。如果我公开 var,我可以这样做:

A objectA = [ [ A alloc ] init ];
NSLog( @"%d", objectA->var );
objectA->var = someNumber;

如果我把它变成一个属性,我将不得不做更多这样的事情:

A objectA = [ [ A alloc ] init ];
NSLog( @"%d", objectA.var ); // dot-syntax
NSLog( @"%d", [ objectA var ] ); // get-syntax
[ objectA setVar: someNumber ];

我都试过了,它们都工作得很好,但我的问题是使用旧式指针表示法访问对象内部的变量有多危险?我以后是否需要通过标准化我的方法访问来担心我现在应该处理的事情?或者,只要它有效,我就可以侥幸逃脱吗?

4

5 回答 5

12

自从几天前我对另一个问题的评论开始了类似的讨论以来,我将投入自己的 2 美分。

您应该始终使用访问器方法在该对象的类的实现之外设置和获取该对象的属性。即使在所述类的实现内部,您也应该几乎总是使用访问器方法来访问类的属性。

以下是一些原因列表:

  • 封装一个类可以公开一个在外部世界看起来就像任何其他属性一样的属性,但在内部没有相应的 ivar 支持。也许它实际上对另一个内部属性进行了一些转换。更重要的是,这个实现可能会在某个时候改变。在 OO 代码中封装的主要原因之一是可以更改类的内部实现,而无需该类的外部用户进行更改。(我今天早上在一个重要的旧类中做了这样的改变——从一个 ivar 到一个完全不同的支持方法。如果一堆其他类,甚至同一个类中的方法正在执行直接 ivar 访问,我将不得不更改比我做的更多的代码。)

  • 内存管理。这对 ARC 来说没什么大不了的,因为无论哪种方式都可以正确处理(大部分情况见下文),但是通过手动内存管理,属性的访问器方法将负责正确保留和释放底层对象. 将这段代码保存在一个地方可以大大降低内存管理错误(泄漏和过度释放)的可能性,更容易阅读,更容易修改,更少冗长的代码。即使启用了 ARC,使用复制行为声明的属性也依赖于 setter 方法来执行复制,如果您执行直接 ivar 访问,则可以绕过它。

  • 现场自定义行为。这实际上只是封装的另一部分。当调用相应的 setter 时,一个类想要做的不仅仅是将 ivar 设置为新值是很常见的。[self setNeedsDisplay]一个非常常见的简单示例是当影响其外观的属性发生更改时调用 NSView 子类。如果您不调用 setter,而是直接设置 ivar,您将完全绕过此行为。同样,您可能认为只要您知道 setter 不需要做这样的事情就可以了,但是需求会发生变化,并且从一开始就使用 setter,您可以更轻松地进行更改。

  • 获取/延迟实例化的自定义行为。最常见的原因之一是进行惰性实例化。您为属性实现 getter 方法,以便它检查底层 ivar 是否为 nil,如果是,它首先在返回 ivar 之前对其进行初始化。下次调用它时,将设置 ivar,因此初始化不会再次发生。这是一种将昂贵(CPU 密集型和/或内存密集型)对象的创建推迟到实际需要时的简单方法。例如,它通常作为性能优化来缩短启动时间,这是一个完美的封装示例,允许对类的实现进行简单改进,而不会破坏外部代码对类的现有使用。

  • KVO合规性。Objective-C 提供了一种称为键值观察的机制,它允许一个对象在另一个对象的给定属性发生更改时请求通知。如果您使用正确命名的访问器或合成的@properties,您将自动获得对 KVO 的支持。但是,要使 KVO 工作,必须实际调用访问器方法。如果直接更改 ivar,则不会通知该 ivar 对应属性的观察者。无论您是设置另一个对象的属性还是自己的属性,您都不知道观察者是否已注册该属性的更改,并且您正在缩短他们收到更改通知的时间。

  • 可读性。这并不完全适用于您的objectA->var示例,它更适用于类自己的实现(var = ...)中的直接 ivar 访问,其中self->由编译器隐含/插入。问题是,特别是在长方法中,您可能会看到一个赋值语句,并且一眼不知道被赋值的变量是当前作用域的本地变量还是实例变量。这可以通过命名约定来缓解,例如在 ivars 前加下划线、匈牙利符号等,但 Objective-C 约定仍然意味着最好使用self.var = ...or [self setVar:...](顺便说一下,它们在语义上完全相同)。

  • 为什么不?有些不使用访问器方法,但我想不出任何好的理由。键入并没有真正快得多,因为在变量名前加上“self”。只是不需要那么多打字。由于您添加了额外的 ObjC 消息发送,因此调用访问器会降低性能。但是,这个惩罚非常小,当然,过早的优化是一件坏事。访问器方法调用的开销足以严重影响应用程序性能的情况很少见。

请记住,关于在类的实现中使用直接 ivar 访问,我使用了“几乎”限定符。如果您实现自定义访问器方法(与 @synthesizing 属性相反),您当然必须直接访问访问器实现中的 ivar。此外,有些人不同意,但在类的初始化程序 ( -init) 方法中直接设置 ivars 是个好主意(也是 Apple 的建议)。这样做的原因是您可能希望在类未完全初始化时避免 setter 副作用。例如,您不希望 KVO 观察者在发现通知对象处于不一致/部分初始化状态时收到更改通知。

于 2013-02-15T22:31:09.153 回答
5

使用旧式指针表示法访问对象内部的变量有多危险?

非常危险。

我以后是否需要通过标准化我的方法访问来担心我现在应该处理的事情?

是的。这些其他需要担心的事情包括:内存管理(Memory.Management!!!),“Key-Value Observing”不起作用等。所有这些都是因为使用->,您直接访问实例变量(一些过于严格的 OO 粉丝甚至会说它违反了封装,它确实经常这样做),而点符号调用正确的 getter 和 setter 方法(它们负责体面地管理内存,通知 KVO 侦听器等)

因此,总而言之,使用访问器方法(如果您愿意,还可以使用点符号),并且不要直接访问实例变量。

于 2013-02-15T21:55:40.773 回答
2

您应该使用属性表。这种方法更标准,因此在团队中可以很好地发挥作用。它还有一个明显的优势,即允许您稍后更改实现而不更改调用者使用的接口。

于 2013-02-15T21:56:10.740 回答
2

在 ARC 之前,属性类型引用有很大的不同(使用[thing setMyVar:value];orthing.myVar = value;观察保留/释放语义,而直接 iVar 引用 ( thing ->myVar = value;) 则没有。然而,它与 ARC 混淆了很多。

于 2013-02-15T21:58:57.993 回答
0

是的,使用旧式指针表示法更危险。在 Objective-C 中,使用点表示法与使用“setVar”方法完全相同。

[objectA setVar:someNumber]; // Exactly the same as objectA.var = someNumber;

当我说“完全一样”时,我是认真的。当您使用objectA.var = someNumber时,您正在调用 setter 方法(您可以在修改实例变量之前修改和验证值)。使用指针式分配时,您可以直接修改,允许根据应用程序的业务逻辑保存可能错误的值。

换句话说:始终使用 Objective-C 的 setter/getter 约定(它们出于某种原因存在)。

我特别喜欢使用objectA.var = someNumber语法,因为它更容易被其他语言程序员理解(Objective-C 方法调用很奇怪,如果你不知道 Objective-C 创建了 setter/getter自动合成属性时,很难阅读它们)。

于 2013-05-22T02:09:50.690 回答