object-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 в getter причинява безкраен цикъл. Какъв е правилният начин да напиша getter и setter, така че да не получавам безкраен цикъл?


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


Отговори (3)


Трябва да имате директен достъп до ivar като _scale. Вашият getter/setter тогава ще изглежда така:

Актуализация: Както отбелязва @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
Ако замените getter и setter, няма нужда от @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 обикновено е много лошо нещо за използване за вашия тествайте, защото може да искате 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 за обяснението. Това е, което измислих, но не знаех, че писането на ваш собствен getter/setter означава, че трябва да синтезирате свойството. - person soleil; 31.10.2012
comment
@soleil Силно ви препоръчвам да изпробвате това, което поставих под заглавието По-добър начин. Вие вършите доста повече работа, отколкото наистина трябва. - person Nate Chandler; 31.10.2012
comment
Защо премахването само на getter елиминира @synthesize? - person soleil; 31.10.2012
comment
@soleil Имахте нужда само от @synthesize, защото бяхте заменили и сетера и гетера. След като елиминирате едно от тези замени, получавате променливата на екземпляра _scale, създадена за вас автоматично. - person Nate Chandler; 31.10.2012