Объявление свойства - значения ivar и getter не совпадают - PullRequest
10 голосов
/ 07 декабря 2011

У меня есть сомнения по поводу объявления недвижимости

Обзор:

  • класс "A" является родительским классом со свойством только для чтения int n1;
  • класс "B" является подклассом, который повторно объявляет свойство как чтение-запись
  • с использованием метода установки класса "B" значение свойства устанавливается как 20
  • когда я печатаю значение, используя геттер и переменную экземпляра, я получаю разные значения

Обращает на себя внимание: - Управление памятью = ARC (автоматический подсчет ссылок)

Вопрос:

  • Когда я печатаю значения self.n1 и _n1, почему я получаю разные значения?
  • Мое ожидаемое поведение и реальное поведение не совпадают почему (пожалуйста, прокрутите вниз, чтобы увидеть фактическое и ожидаемое)?

Код: (в отдельных файлах)

хиджры

#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

Ответы [ 2 ]

7 голосов
/ 07 декабря 2011

Есть два способа сделать это.Ни в том, ни в другом случае вы не вызываете @synthesize в подклассе. Я удивлен, что компилирует для вас.Я ожидаю ошибку типа «Свойство n1» при попытке использовать ivar «_n1», объявленную в суперклассе «A» ». В любом случае это определенно не то, что вы действительно можете сделать, поэтому вы видите странное поведение,(Я вспомнил, почему вы не видите эту ошибку; это из-за отдельных модулей компиляции. Вы просто получаете разные ивары.)

Во-первых, вам нужно понять @dyanmic.Это способ сказать компилятору: «Да, я знаю, что вы не видите здесь реализацию требуемого метода; я обещаю, что он будет там во время выполнения».В подклассе вы будете использовать @dynamic, чтобы сообщить компилятору о том, что унаследовать n1.

@implementation B
@dynamic n1;
@end

нормально. Теперь вам нужно предоставить метод setN1:.IMO, подклассы не должны портиться с иварами их суперклассов, поэтому я одобряю тот факт, что синтезированные ивары отмечены @private.Через секунду я расскажу вам, как отменить это, но сейчас давайте разберемся с моим предпочтительным решением:

  • Реализация setN1: как частного метода в A.
  • Выставить его в B.

А *

@interface A : NSObject
@property (readonly) int n1;
- (void) display;
@end

Am

#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

Bh

#import "A.h"    
@interface B : A
@property (readwrite) int n1; // Tell the world about setN1:
@end

Bm

#import "B.h"
@implementation B
@dynamic n1; // Yes compiler, setN1: exists. I promise.
@end

Теперь некоторые люди считают, что для подклассов нормально связываться с иварами их суперкласса.Эти люди не правы (хорошо, ИМХО ...), но это возможно в ObjC.Вам просто нужно объявить ивар @protected.Это значение по умолчанию, когда вы объявляете ivars непосредственно в @interface (одна из многих причин, по которой вам больше не следует делать это).Это выглядело бы так:

Ах

@interface A : NSObject {
  int _n1;
}
...

Am - убрать дополнительное расширение класса, которое делает n1 доступным для записи в суперклассе.

Bh - без изменений

Bm

@implementation B
@dynamic n1;

- (void)setN1:(int)n1 {
  _n1 = n1;
}
@end
2 голосов
/ 07 декабря 2011

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

Вот что происходит: каждая из двух директив @synthesize создает скрытую переменную _n в своем соответствующем классе.Кроме того, директива синтезирует геттер для n1 в A и пару геттер / установщик в B.Получатель n1 в B переопределяет геттер n1 в A;сеттер этого не делает, потому что нечего переопределять.

На этом этапе A _n1 в B становится осиротевшим: ни получатель n1, ни его установщик не ссылаются на него.Установщик ссылается на B _n1, а не A.Вот почему вы видите разные значения, напечатанные в методе display из A.Помещение метода в B ведет себя так, как вы ожидаете.

РЕДАКТИРОВАТЬ:

Естественно, следующий вопрос, как сделать поведение, которое вы хотите.Оказывается, все просто: не синтезируйте свойство в B, а реализуйте установщик _n1 в файле реализации A (без помещения его в интерфейс, чтобы он оставался доступным только для чтения дляклиенты вашего интерфейса).

// This goes in A.m without a declaration in A.h
- (void) setN1:(int)n1 {
    _n1 = n1;
}
...