在Objective-C中使用指针式赋值与setter-methods有多危险?

Mik*_*ell 2 methods multithreading pointers objective-c synthesize

可以说我有一个简单的类,如下所示:

@interface A { 
// @public
    int var;
}
// @property(some_property) int var;
@end
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

我已经尝试了两种并且它们工作正常但我的问题是使用旧式指针表示法来访问对象内部的变量有多危险?以后我是否需要担心我现在应该通过标准化我的方法访问来解决这个问题?或者我可以逃脱这样做但是我想要它只要有效吗?

And*_*sen 12

自从我在另一个问题上的评论在几天前开始类似的讨论后,我将自己投2美分.

您应始终使用访问器方法在该对象的类的实现之外设置和获取对象的属性.你应该几乎总是使用存取方法来访问甚至说类的实现内部类的属性.

以下列出了一些原因:

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

  • 内存管理.这与ARC的处理并不是很大,因为无论哪种方式(通常见下文)都可以正确处理,但是通过手动内存管理,属性的访问器方法将负责正确保留和释放底层对象.将此代码保存在一个位置可以大大降低内存管理错误(泄漏和过度发布)的可能性,更易于阅读,更易于修改,代码更简洁.即使启用了ARC,使用复制行为声明的属性依赖于setter方法来执行复制,如果直接进行ivar访问,则会绕过该属性.

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

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

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

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

  • 为什么不?有些人不使用访问器方法,但我想不出有什么好的理由.输入的速度并不快,因为用"self"作为变量名前缀.只是不打那么多打字.由于您添加了额外的ObjC消息发送,因此调用访问器会涉及性能损失.然而,这种惩罚是非常小的,当然,过早优化是一件坏事.访问器方法调用的开销足以严重影响应用程序性能,这是非常罕见的.

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


小智 5

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

非常危险.

以后我是否需要担心我现在应该通过标准化我的方法访问来解决这个问题?

是.这些需要担心的其他事情包括:内存管理(内存管理!!!),"键值观察"无法正常工作等等.所有这些都是因为使用->,您直接访问实例变量(一些过于严格的OO粉丝甚至会说它违反封装,它确实经常这样做,而点符号调用适当的getter和setter方法(负责管理内存,通知KVO听众等等)

总而言之,使用访问器方法(如果您愿意,还使用点表示法),并且不要直接访问实例变量.

  • 您可以*.但是*不要*因为提到的所有其他原因.还包括"维护和重构".如果你的课程现在不需要KVO*但将来会有,那么你必须确保找到每个最后的` - >`样式参考或者你已经被软化了. (2认同)