свойства target-c - геттеры и сеттеры

В подклассе UIView у меня есть это свойство:

@property (nonatomic) CGFloat scale;

#define DEFAULT_SCALE 0.90

и этот геттер и сеттер:

-(CGFloat)scale
{
    if (!self.scale) {
        return DEFAULT_SCALE;
    }else{
        return self.scale;
    }
}

-(void)setScale:(CGFloat)scale
{
    if (scale != self.scale) {
        self.scale = scale;
        [self setNeedsDisplay];
    }

}

Это неправильно, потому что, например, проверка self.scale в геттере вызывает бесконечный цикл. Как правильно написать геттер и сеттер, чтобы не было бесконечного цикла?


person soleil    schedule 31.10.2012    source источник


Ответы (3)


Вы должны иметь доступ к ivar напрямую как _scale. Тогда ваш геттер/сеттер будет выглядеть так:

Обновление: как отмечает @ wattson12 в комментариях ниже, вам нужно будет добавить @synthesize к своей реализации.

@synthesize scale = _scale;

-(CGFloat)scale
{
    if (!_scale) {
        return DEFAULT_SCALE;
    }else{
        return _scale;
    }
}

-(void)setScale:(CGFloat)scale
{
    if (scale != _scale) {
        _scale = scale;
        [self setNeedsDisplay];
    }

}
person mttrb    schedule 31.10.2012
comment
Я использую необъявленный идентификатор '_scale' - person soleil; 31.10.2012
comment
Вы использовали @synthesize для своей собственности? Вы создали переменную scale в своем интерфейсе? - person mttrb; 31.10.2012
comment
если вы переопределите как сеттер, так и геттер, вы не получите функцию автоматического синтеза, вам нужно будет добавить @synthesize scale = _scale; - person wattson12; 31.10.2012
comment
Да, это была проблема. Обычно мне не нужно синтезировать, потому что Xcode делает это автоматически. - person soleil; 31.10.2012
comment
Если вы переопределяете геттер и сеттер, @synthesize не нужен. Просто добавьте свой собственный ivar, и методы будут использовать ivar. - person rmaddy; 31.10.2012

хорошо, я могу придумать около 3 способов сделать это и быть в курсе.

@interface someClass
{
    BOOL useCustomScale;
}
@property float scale;
@end
@implimentation someClass
-(float)scale
{
    if(useCustomScale)
    {return scale;}
    return defaultScale;
}
-(void) setScale: (float)someScale
{
    useCustomScale = YES;
    scale = someScale
}

иначе вы могли бы использовать NSNumber для поддержки значения масштаба...
иначе вы могли бы инициализировать масштаб до -1 и сделать это недопустимым значением в установщике.
0, как правило, очень плохо использовать для вашего test, потому что вы можете очень захотеть, чтобы 0 было допустимым значением.

person Grady Player    schedule 31.10.2012

Поначалу запись через точку может немного ввести в заблуждение.

Сеттер

В строке, которую вы разместили здесь

self.scale = scale;

вы не назначаете локальную переменную. На самом деле вы отправляете сообщение -setScale: пользователю self. Эта строка эквивалентна

[self setScale:scale];

Поскольку вы вызываете -setScale: из -setScale:, вы получаете эту бесконечную рекурсию.

Что вам нужно сделать, так это установить переменную экземпляра в вашем сеттере (вместо того, чтобы вызывать ваш сеттер изнутри). Обычно, просто написав

@property (nonatomic) CGFloat scale;

вы создали переменную экземпляра _scale. Однако, поскольку вы также переопределили и -scale, и -setScale:, эта переменная экземпляра не будет создана. Поэтому вам нужно будет добавить переменную экземпляра самостоятельно. В объявлении вашего класса @interface (или, альтернативно, в расширение класса @interface)

//If adding the instance variable to the class declaration:
@interface MyClass : Superclass
{
    //....
    CGFloat _scale;
}
//....
@end

После этого достаточно изменить строку на

_scale = scale;

Добытчик

Есть еще две проблемные строки, которые вы опубликовали, в геттере. Первый

return self.scale;

внутри - (CGFloat)scale. Как и раньше, это обозначение через точку не означает того, что вы можете подумать. На самом деле это означает

return [self scale];

Как и прежде, это вызывает бесконечную рекурсию. Второй

if (!self.scale) {

Это проблема по той же причине: выражение self.scale при оценке равно [самомасштаб]. Опять же, это вызывает бесконечную рекурсию. Исправление для обоих из них состоит в том, чтобы заменить self.scale на _scale, оставив вам этот геттер:

- (CGFloat)scale
{
    if (!_scale) {//Since CGFloat is not an object, this means <<if (_scale == 0) {>>
        return DEFAULT_SCALE;
    } else {
        return _scale
    }
}

Лучший способ

Ты делаешь здесь гораздо больше работы, чем должен. Было бы намного лучше воспользоваться вашим инициализатором:

- (id)initWithFrame:(NSRect)frame
{
    self = [super initWithFrame:frame];
    {
        self.scale = DEFAULT_SCALE;
    }
}

Это гарантирует, что если scale не установлено, будет возвращено DEFAULT_SCALE. Это позволяет полностью исключить геттер (и, следовательно, @synthesize). Поскольку вы вызываете -setNeedsDisplay в сеттере, он вам все равно понадобится.

- (void)setScale:(CGFloat)scale
{
    if (_scale != scale) {
        _scale = scale;
        [self setNeedsDisplay];
    }
}
person Nate Chandler    schedule 31.10.2012
comment
+1 за объяснение. Это то, что я понял, но я не знал, что написание собственного геттера/сеттера означает, что вам нужно синтезировать свойство. - person soleil; 31.10.2012
comment
@soleil Я настоятельно рекомендую вам попробовать то, что я поместил под заголовком «Лучший способ». Вы делаете немного больше работы, чем вам действительно нужно. - person Nate Chandler; 31.10.2012
comment
Почему удаление только геттера устраняет @synthesize? - person soleil; 31.10.2012
comment
@soleil Вам нужен был только @synthesize, потому что вы переопределили как установщик и геттер. Как только вы удалите одно из этих переопределений, вы автоматически создадите переменную экземпляра _scale. - person Nate Chandler; 31.10.2012