Объявление свойства - значения ivar и геттера не совпадают
У меня есть сомнения относительно переоценки свойств
Обзор:
- класс "A" - это родительский класс с свойством readonly int n1;
- класс "B" - это подкласс, который переопределяет свойство как прочитанное write
- с помощью установщика класса "B" значение свойства устанавливается как 20
- когда я печатаю значение с помощью getter и переменной экземпляра, я, кажется, получаю разные значения
Замечания: - Управление памятью = ARC (автоматический подсчет ссылок)
Вопрос:
- Когда я печатаю значения self.n1 и _n1, почему я получаю разные значения?
- Мое ожидаемое поведение и фактическое поведение не совпадают с тем, почему (Pls прокручивается вниз, чтобы увидеть фактический vs ожидаемый)?
Код: (в отдельных файлах)
хиджры
#import<Foundation/Foundation.h>
@interface A : NSObject
@property (readonly) int n1;
- (void) display;
@end
a.m
#import "A.h"
@implementation A
@synthesize n1 = _n1;
- (void) display
{
printf("_n1 = %i\n", _n1); //I expected _n1 and self.n1 to display the same value
printf("self.n1 = %i\n\n", self.n1); //but they seem to display different values
}
@end
B.h
#import"A.h"
@interface B : A
@property (readwrite) int n1;
@end
B.m
#import"B.h"
@implementation B
@synthesize n1 = _n1;
@end
test.m
#import"B.h"
int main()
{
system("clear");
B* b1 = [[B alloc] init];
b1.n1 = 20;
[b1 display]; //Doubt - my expected behavior is different from actual behavior
return(0);
}
Ожидаемое поведение:
_n1 = 20
self.n1 = 20
Фактическое поведение:
_n1 = 0
self.n1 = 20
Ответы
Ответ 1
Есть два способа обойти это. В любом случае вы не называете @synthesize
в подклассе. Я удивлен, что компилируется для вас. Я бы ожидал ошибку, например "Свойство" n1, пытающееся использовать ivar "_n1", объявленный в суперклассе "A". В любом случае это определенно не то, что вы действительно можете сделать, поэтому вы видите странное поведение. (Я вспомнил, почему вы не видите эту ошибку, из-за отдельных единиц компиляции. Вы просто сворачиваете с разными иварами.)
Во-первых, вам нужно понять @dyanmic
. Это способ сообщить компилятору "да, я знаю, что вы не видите реализацию для требуемого метода здесь, я обещаю, что он будет там во время выполнения". В подклассе вы будете использовать @dynamic
, чтобы компилятор знал, что он наследует n1
.
@implementation B
@dynamic n1;
@end
Теперь вам нужно предоставить метод setN1:
. ИМО, подклассы не должны вступать в беспорядки со своими надклассами ivars, поэтому я одобряю тот факт, что синтезированные ivars отмечены @private
. Через секунду я расскажу вам, как отменить это, но теперь разрешите свое предпочтительное решение:
- Внедрите
setN1:
в качестве частного метода в A
.
- Выставить его в
B
.
хиджры
@interface A : NSObject
@property (readonly) int n1;
- (void) display;
@end
a.m
#import "A.h"
@interface A () // Private class extension, causes setN1: to be created but not exposed.
@property (readwrite) int n1;
@end
@implementation A
@synthesize n1 = _n1;
- (void) display {
...
}
@end
B.h
#import "A.h"
@interface B : A
@property (readwrite) int n1; // Tell the world about setN1:
@end
B.m
#import "B.h"
@implementation B
@dynamic n1; // Yes compiler, setN1: exists. I promise.
@end
Теперь некоторые люди думают, что это нормально для подклассов, чтобы возиться со своими надклассами ivars. Эти люди ошибаются (хорошо, ИМХО...), но это возможно в ObjC. Вам просто нужно объявить ivar @protected
. Это значение по умолчанию, когда вы объявляете ivars непосредственно в @interface
(одна из многих причин вам больше не нужно делать). Это будет выглядеть так:
хиджры
@interface A : NSObject {
int _n1;
}
...
A.m - удалить дополнительное расширение класса, которое делает n1 доступным для записи в суперклассе.
B.h - без изменений
B.m
@implementation B
@dynamic n1;
- (void)setN1:(int)n1 {
_n1 = n1;
}
@end
Ответ 2
Я скопировал ваш код и проверил поведение, которое вы получаете. Я могу объяснить механику этого, но не логику этого.
Вот что происходит: каждая из двух директив @synthesize
создает скрытую переменную _n
в соответствующем классе. Кроме того, директива синтезирует геттер для n1
в A
и пару геттер/сеттер в B
. Геттер n1
в B
переопределяет геттер n1
в A
; сеттер не делает этого, потому что нечего переопределять.
В этот момент A
_n1
в B
становится сиротой: ни геттер n1
, ни его сеттер не ссылаются на него. Установщик ссылается B
_n1
, а не A
. Вот почему вы видите разные значения, напечатанные в методе display
A
. Ввод метода в B
ведет себя так, как вы ожидали.
EDIT:
Естественно, следующий вопрос заключается в том, как сделать нужное поведение. Это оказывается простым: не синтезируйте свойство в B
и не создайте установщик _n1
в файле реализации A
(не помещая его в интерфейс, чтобы он оставался доступным только для чтения клиентам ваш интерфейс).
// This goes in A.m without a declaration in A.h
- (void) setN1:(int)n1 {
_n1 = n1;
}