假设我有一个简单的类,如下所示:
@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 ];
我已经尝试了两种并且它们工作正常但我的问题是使用旧式指针表示法来访问对象内部的变量有多危险?以后我是否需要担心我现在应该通过标准化我的方法访问来解决这个问题?或者我可以逃避这样做但是我想要它只要有效吗?
答案 0 :(得分:11)
自从我的另一个问题的评论在几天前开始类似的讨论之后,我将自己投2美分。
你应该总是使用访问器方法在该对象的类的实现之外设置和获取对象的属性。你应该几乎总是使用访问器方法来访问类的属性,即使在所述类的实现中也是如此。
以下列出了一些原因:
Encapsulation。一个类可能会向外部世界公开一个看起来就像任何其他属性一样的属性,但内部没有相应的ivar支持。也许它实际上对另一个内部财产进行了一些转变。更重要的是,这种实现可能会在某些时候发生变化。封装在OO代码中的一个主要原因是可以更改类的内部实现,而无需该类的外部用户进行更改。 (我做了这样一个改变 - 从一个ivar到一个完全不同的支持方法 - 在今天早上的一个重要的,老班级。如果一堆其他类,甚至同一类中的方法都在直接进行ivar访问,我将不得不改变比我更多的代码。)
内存管理。这与ARC的交易并不是很重要,因为无论哪种方式(通常见下文)都可以正确处理,但需要手动内存管理,属性的访问器方法将负责正确保留和释放底层对象。将此代码保存在一个位置可以降低 内存管理错误(泄漏和过度发布)的可能性,更易于阅读,更易于修改,代码更简洁。即使启用了ARC,使用复制行为声明的属性也依赖于setter方法来执行复制,如果直接进行ivar访问,则可以绕过它。
设置自定义行为。这实际上只是封装的另一部分。除了在调用相应的setter时将ivar设置为新值之外,一个类想要做一些事情是非常常见的。一个非常常见的简单示例是,当影响其外观的属性发生更改时,NSView子类将调用[self setNeedsDisplay]
。如果您不打电话给设定者,而是直接设置ivar,您将完全绕过此行为。再说一遍,只要你知道setter不需要做这样的事情,但是需求发生变化,并且从一开始就使用setter,你就可以认为它很好。更简单。
get / lazy instantiation上的自定义行为。最常见的原因之一是进行延迟实例化。为属性实现getter方法,以便检查底层ivar是否为nil,如果是,则首先在返回之前初始化ivar。下次它被调用时,将设置ivar,以便初始化不会再次发生。这是一种简单的方法,可以推迟创建昂贵的(CPU密集型和/或内存密集型)对象,直到实际需要它们为止。例如,它通常作为性能优化来完成,以改善启动时间,这是一个完美的封装示例,允许简单改进类的实现,而不会破坏外部代码的现有类使用
KVO合规性。 Objective-C提供了一种名为Key Value Observing的机制,允许一个对象在另一个对象的给定属性发生更改时请求通知。如果您使用properly named访问者或合成的@properties,您将自动获得对KVO的支持。但是,要使KVO工作,必须实际调用访问器方法。如果您直接更改ivar,则该ivar相应属性的观察者将不得到通知。无论您是在自己设置其他对象的财产还是财产,您都不知道观察员是否已注册了该属性的更改,并且您已将其通知变更短路
可读性。这并不完全适用于您的objectA->var
示例,它更适用于课程中的直接ivar访问自己的实现(var = ...
),其中self->
由编译器隐含/插入。问题是,特别是在long方法中,您可能会看到赋值语句,并且一眼就看不到所分配的变量是当前作用域的本地变量还是实例变量。这可以通过命名惯例来缓解,例如使用下划线,Hungarian notation等添加ivars前缀,但仍然使用Objective-C约定意味着最好使用self.var = ...
或[self setVar:...]
(顺便说一句,语义完全相同)。
为什么不呢?有些人不使用访问方法,但我无法想出任何理由。输入的速度并不快,因为在变量名前加上“自我”。'只是不打那么多的打字。由于您添加了额外的ObjC消息发送,因此在调用访问器时会涉及性能损失。然而,这种惩罚是非常小的,当然,过早优化是一件坏事。 非常非常罕见,访问者方法调用的开销足以严重影响应用程序性能。
请记住,我使用过几乎'关于在课堂实施中使用直接ivar访问的限定词。如果您实现自定义访问器方法(而不是@synthesizing属性),您将必须直接访问访问器内的ivar'当然是实现。此外,有些人不同意,但在类的初始化器(-init
)方法中直接设置ivars是一个好主意(以及Apple的建议)。这样做的原因是,您可能希望在类未完全初始化时避免使用setter副作用。例如,当他们发现通知对象处于不一致/部分初始化状态时,您不希望KVO观察者收到更改通知。
答案 1 :(得分:5)
使用旧式指针表示法访问对象内的变量有多危险?
非常危险。
以后我是否需要担心我现在应该通过标准化方法的访问来解决这个问题?
是。其他需要担心的事情包括:内存管理( 内存。管理!!! ),“Key-Value Observing”无法正常工作等等。所有这些都是因为使用->
,你直接访问实例变量(一些过于严格的OO粉丝甚至会说它违反了封装,它确实经常这样做),而点符号则调用正确的getter和setter方法(负责正确地管理记忆,通知KVO听众等等。)
总而言之,使用访问器方法(如果您愿意,还使用点表示法),并且不要直接访问实例变量。
答案 2 :(得分:2)
您应该使用属性表单。这种方法更加标准,因此它在团队中发挥得很好。它还具有明显的优势,允许您稍后更改实现,而无需更改调用者使用的接口。
答案 3 :(得分:2)
在ARC之前存在 大 差异,因为属性类型引用(使用[thing setMyVar:value];
或thing.myVar = value;
观察保留/释放语义虽然直接的iVar参考(thing ->myVar = value;
)没有。但是,它与ARC相比更加混乱。
答案 4 :(得分: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 / getters,它们更难以读取。)