Объявление свойства - значения 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;
}