什么时候覆盖客观c getters

时间:2012-03-21 02:25:59

标签: objective-c ios xcode properties getter-setter

在过去的一年里,我第一次与其他人一起开展了一些Objective-C项目。

偶尔(并且越来越多)我看到其他人重写getter / accessor方法,并且在此方法中包含实现代码!对我来说,这是一个疯狂的城镇,因为这是拥有一个二传手的全部意义......这也意味着设置在setter中的属性将在getter中被覆盖,因此毫无意义。

这些人表现得很糟糕,还是我失去了什么?是否需要覆盖合成属性的getter方法?

示例:

@synthesize width;

- (CGFloat)width {
  NSNumber *userWidth = [[NSUserDefaults standardUserDefaults] objectForKey:@"USER_WIDTH"];

  if (userWidth != nil) 
  {
    width = [NSNumber floatValue];
  }     

  //Shouldn't the above lines be done in the SETTER? I have SET the property!
  //Why would I ever need to write anything BUT the below line??       
  return width;
}

- (void)setWidth:(CGFloat)newWidth {
  //THIS is where you do the the setting!
  width = newWidth;
}

更新

好宽度是个坏例子。太多的人都陷入了“变量是什么”和“不包括get-objective-c访问器”的语义。所以我更新了上面的例子来忽略不相关的语义,并专注于这个概念。当你想要覆盖 GETTER (不是setter,只有getter。我多次覆盖setter,这个问题是关于getter的)时,这个概念是......吗?

返回另一个属性,如图层(如下所述)是一个真实的例子。但更具体地说,是否需要设置 GETTER 中的属性?这是我所看到的一些奇怪之处,所以我更新了上面的getter来从NSUserDefaults中提取一个值来帮助我... ...

5 个答案:

答案 0 :(得分:12)

首先,Cocoa命名约定会调用getter -width,而不是-getWidth。 “Get”用于填充传递的参数:

- (void) getWidth:(CGFloat *)outWidth
{
    if (outWidth) *outWidth = _width;
}

那说,回到原来的问题:

过去,在@property@synthesize之前,我们必须像上面那样手动编写访问者。

但是,在其他情况下,您可能需要手动编写访问者。

一个常见的做法是延迟初始化直到需要一个值。例如,假设有一个图像需要一段时间才能生成。每次修改将改变图像的属性时,您都不希望立即重绘图像。相反,你可以推迟抽奖直到下一次有人问:

- (UIImage *) imageThatTakesAwhileToGenerate
{
    if (!_imageThatTakesAwhileToGenerate) {
        // set it here
    } 

    return _imageThatTakesAwhileToGenerate;
} 


- (void) setColorOfImage:(UIColor *)color
{
    if (_color != color) {
        [_color release];
        _color = [color retain];

        // Invalidate _imageThatTakesAwhileToGenerate, we will recreate it the next time that the accessor is called
        [_imageThatTakesAwhileToGenerate release];
        _imageThatTakesAwhileToGenerate = nil;
    }
}

另一个用途是将accessor / mutator的实现转发给另一个类。例如,UIView会将其许多属性转发给支持CALayer

// Not actual UIKit implementation, but similar:
- (CGRect) bounds { return [[self layer] bounds]; }
- (void) setBounds:(CGRect)bounds { [[self layer] setBounds:bounds]; }
- (void) setHidden:(BOOL)hidden { [[self layer] setHidden:hidden]; }
- (BOOL) isHidden { return [[self layer] isHidden]; }
- (void) setClipsToBounds:(BOOL)clipsToBounds { [[self layer] setMasksToBounds:clipsToBounds]; }
- (BOOL) clipsToBounds { return [[self layer] masksToBounds]; }

更新到提问者的更新:

在您的更新中,看起来有问题的代码是尝试使用NSUserDefaults保持宽度值,或者它试图允许用户指定自定义值来覆盖所有返回的宽度。如果是后者,你的例子很好(虽然我会限制这种做法,因为它可能会引起新人的混淆)。

如果是前者,您希望从NSUserDefaults加载一次值,并将新值保存回设置器中的NSUserDefaults。例如:

static NSString * const sUserWidthKey = @"USER_WIDTH";

@implementation Foo {
    CGFloat _width;
    BOOL _isWidthIvarTheSameAsTruthValue;
}

@synthesize width = _width;

- (CGFloat) width
{
    if (!_isWidthIvarTheSameAsTruthValue) {
        NSNumber *userWidth = [[NSUserDefaults standardUserDefaults] objectForKey:sUserWidthKey];
        if (userWidth != nil) _width = [NSNumber doubleValue];
        _isWidthIvarTheSameAsTruthValue = YES;
    }

    return _width;
}

- (void) setWidth:(CGFloat)newWidth
{
    if (_width != newWidth) {
        _width = newWidth;
        NSNumber *userWidthNumber = [NSNumber numberWithDouble:_width];
        [[NSUserDefaults standardUserDefaults] setObject:userWidthNumber forKey:sUserWidthKey];
        _isWidthIvarTheSameAsTruthValue = YES;
    }
}

@end

_width ivar被用作缓存。事实存储在NSUserDefaults中。

注意:我在此示例中使用的是NSUserDefaults,因为您在自己的身上使用过NSUserDefaults。在实践中,我更喜欢不将NSUserDefault与我的访问器混合使用;)

答案 1 :(得分:5)

第一个问题是你不想使用getWidth。 objC中的模式是name和setName。不要使用getName。它弄乱了绑定和KVO。

另外,如果它只是设置/获取iVar,则没有理由覆盖。如果您正在进行额外的处理/验证,那么可以覆盖。

编辑:

您还应该尝试避免在getter中设置数据和执行繁重的处理。 getter应该封装一些状态并返回数据。期望它的重量非常轻。重复处理和/或修改应在方法或设定者中进行。例如,人们在getter上设置调试监视,并且不期望重处理和修改状态。

答案 2 :(得分:1)

懒惰地创建属性对象的情况如何? 我经常使用这种模式,也用于Xcode的CoreData模板等。

- (NSString *)string
{
    if (!_string) {
        // Create the string property lazily
        // Create is using some other internal, etc values
        _string = [NSString alloc] initWith...]
    }
    return _string;
}

此外:

- (void)setString:(NSString *)string
{
    if (![string isEqualToString:_string]) {
        // Probably you want to make your property observable here too :)

        [_setString release];
        _setString = [string retain];

        // Update other things that depend on _string for example redraw the view, etc
        [self setNeedsDisplay];
    }
}

答案 3 :(得分:0)

有很多理由要覆盖getter和setter方法,例如我在制作自定义UITableViewCell对象时覆盖setter方法,这样我就可以设置属性,然后在setter方法中,它会自动更新标签或者在单元格内的某些东西,而不是我必须在设置属性后调用更新函数。

如果您希望以不同于接收信息的方式存储信息,则可能需要覆盖getter,例如可能是电话号码对象,您可以将其存储为5551234567,它将自动检索为555-123-4567或类似的东西。我自己很少覆盖getter方法,但是我经常覆盖setter方法。

答案 4 :(得分:0)

这是面向对象编程中完全可以接受的做法。但是,需要注意副作用。例如,您不应该在setter方法中执行类似网络访问的操作。

但是,在上面列出的代码中,因为它们与合成方法没有做任何不同的事情,所以没有理由包含这些实现。他们只是弄乱了代码。