如何干净地覆盖属性设置器?

Posted

技术标签:

【中文标题】如何干净地覆盖属性设置器?【英文标题】:How do I cleanly override a property setter? 【发布时间】:2011-03-21 15:24:44 【问题描述】:

如果我想定义一个自定义 UIView 子类,它在设置边界时执行某些操作,我该如何覆盖 setter?覆盖 setBounds 似乎很危险,因为如果我理解正确,getter 和 setter 名称不是公共接口的一部分,并且可能随时更改。

当然,我使用class_copyPropertyList在运行时查询类中定义的属性列表,然后查询setter的名称,最后使用class_addMethod添加方法,先获取到之前方法的引用用于调用原始版本。

所有这一切看起来都很老套。有没有一种干净的方法来做我想做的事,保证不会在未来的操作系统版本上中断?谢谢。

【问题讨论】:

bounds 属性是在 UIView 标头中定义的,因此您的评论“getter 和 setter 名称不是公共接口的一部分并且可以随时更改”是不正确的:@property(nonatomic) CGRect bounds 这意味着getter/setter 是 bounds/setBounds: 实例变量也在标题中声明,但这并不意味着它们是公共接口的一部分:) 说developer.apple.com/library/ios/#documentation/uikit/reference/… 是公共接口是否准确?这没有提到 setBounds。 【参考方案1】:

您可以覆盖 setter/getter,而无需查看类的内部状态(即 ivars)——只需在覆盖中调用 super 的方法即可:

- (Thing *)thing 
    // do your extra stuff here
    //  ...

    return [super thing];


- (void)setThing:(Thing *)thing 
    // do your extra stuff here
    //  ...

    [super setThing:thing];

可能适合您的问题的替代方法是使用KVO。

更新

当然,覆盖setBounds 可能没有必要。参见this question -- layoutSubviews 会在帧发生变化时被调用,并且更改边界会导致帧大小也被更新。所以考虑将你的代码放入layoutSubviews

最终更新

好的,这就是为什么 Apple 永远不会突然将某些 @property 项目声明为使用非标准方法名称(正如您所担心的那样):

它会破坏应用商店中的所有内容。

这样想:在编译时,任何使用点符号访问属性的代码,例如obj.x 语法糖被转换为[obj x] 形式的消息。属性也是如此——它们在编译时被转换为常规方法。因此,编译后的二进制文件对点符号和属性一无所知——它们只是调用常规选择器。因此,如果 Apple 发布了 iOS 更新,将一些公共属性声明为具有非标准实现方法,那么应用商店可能会崩溃。一切。在这种情况下,如果您的应用程序像其他应用程序一样坏了,您并没有过错——这将是 Apple 的错,而不是您的错。

【讨论】:

这并没有解决我的问题——如果设置器名称在 iOS 5 中更改为 setViewBounds 怎么办?我在developer.apple.com/library/ios/#documentation/uikit/reference/… 的 UIView 文档的任何地方都找不到名称 setBounds,所以我不相信我可以依赖它。我错过了什么吗? 换句话说,setter 名称是类内部状态的一部分。 @kartick-vaddadi setter 名称源自属性bounds,它是已发布 API 的一部分,因此不太可能无缘无故地更改。设置器名称的模式是setX——这不会改变,因为绝对一切都会破坏,不仅仅是你的代码!我非常有信心 setBoundsbounds 方法不会变成别的东西。 @occulus -- 我的理解是属性名称'bounds'是接口的一部分,而不是实现它的setter的名称。事实上,声明一个属性的全部意义在于人们使用它而不是底层的访问器,对吧?我错过了什么吗? 这里的关键点是为 ivar 'x' 声明一个读/写属性在幕后为您定义了两个方法:x(getter)和 setX:(setter)。声明一个属性实际上为您创建了这两种方法——它是一种语法糖。当您编写点符号(例如“myObject.x = thing”)时,在幕后会转换为对“setX”的调用。这就是 Objective C 做事的方式:点符号和@property 声明是一种语法糖来帮助你。在幕后,这些东西只是成为称为“x”和“setX”的方法,并调用这些方法。【参考方案2】:
@property(nonatomic) CGRect bounds; 

的简写
-(CGRect)bounds; 
-(void)setBounds:(CGRect)bounds; 

,

view.bounds = rect; 

是简写

[view setBounds:rect];

,

CGRect rect = view.bounds;

是简写

CGRect rect = [view bounds];

点符号和@property 声明是语法糖。它们是为了缩短代码和方便。消息和选择器始终位于其下方,并且始终可以依赖它们作为界面的稳定部分,即使不是最稳定的部分。

覆盖“setBounds:”是一种安全的方法。 “setBounds:”在公共接口中没有明确命名,因为它被声明为@property。但是标准是始终创建具有“set”-大写属性名称的设置器(除非它是只读的)。

【讨论】:

请看我上面的cmets。 @property 可以更改为使用不同的 setter 名称。 是的,但苹果不会这样做,因为它会破坏很多东西。他们更有可能更改内部 ivar 变量名称(对您隐藏),但不考虑公共属性名称。 @kartick-vaddadi 它是接口声明的一部分,它是与图书馆消费者的既定合同。依赖它就像依赖任何其他对象或函数一样安全。您不妨担心整个框架中的每一个方法名称。 @Saltymule,请看这个,为方便起见再次粘贴:我的理解是属性名称'bounds'是接口的一部分,而不是实现它的setter的名称。事实上,声明属性的全部意义在于人们使用它而不是底层访问器,对吧?我错过了什么吗? 属性在“引擎盖”下使用访问器。属性不是与访问器“分离”的——属性只是为您提供一种方便的方式,让编译器为您生成标准访问器!【参考方案3】:

在 OSX 中有一个 NSViewBoundsDidChange 通知。这似乎是更好的解决方案,尽管对覆盖@property 方法的担忧似乎没有根据。我在这篇文章中遇到了关于覆盖访问器的相同问题,您已经说服我更喜欢通知。

【讨论】:

以上是关于如何干净地覆盖属性设置器?的主要内容,如果未能解决你的问题,请参考以下文章

iOS Objective C 覆盖属性设置器

WooCommerce 如何在前端获取产品属性的干净标签

如何在 NHibernate 中手动覆盖属性 OnLoad 方法?

Hibernate 错误地在子类而不是超类中寻找属性的设置器?

如何覆盖会话属性?

text 边框的速记属性干净整洁,但是使用此边框SCSS Mixin,您可以轻松地向一侧添加边框或