Почему свойство только для чтения по-прежнему позволяет писать с помощью KVC

Я работаю над главой «Кодирование значения ключа» в «Программировании для Mac OS X». Я создал интерфейс с ползунком и меткой, оба привязаны к фидо, int. Если я установлю для свойства fido значение только для чтения, перемещение ползунка все равно приведет к тому, что метка изменит свое значение. Я предполагал, что получу какую-то ошибку для этого. Если свойство доступно только для чтения, почему ползунок все еще может писать в свойство? Я думал, что в нем не будут создаваться сеттеры, и KVC не будет работать. Спасибо.

Вот код, который я использую:

#import <Cocoa/Cocoa.h>

@interface AppController : NSObject
{
    int fido;
}

@property (readonly, assign) int fido;

@end

#import "AppController.h"

@implementation AppController

@synthesize fido;

- (id)init
{
    [super init];
    [self setValue:[NSNumber numberWithInt:5] forKey:@"fido"];
    NSNumber *n = [self valueForKey:@"fido"];
    NSLog(@"fido = %@", n);
    return self;
}
@end

http://idisk.me.com/nevan/Public/Pictures/Skitch/Window-20091001-174352.png


person nevan king    schedule 01.10.2009    source источник


Ответы (3)


AppController.h:

@interface AppController : NSObject
{
        int fido;
}

@property (readonly, assign) int fido;
@end

импортировать "AppController.h"

@implementation AppController
@synthesize fido;
...
@end

На данный момент вы объявили, что AppController имеет метод -fido, и вы синтезировали этот метод. -setFido: метода нет. Итак, почему следующее «работает»?

- (id)init
{
        if (self=[super init]) {
            [self setValue:[NSNumber numberWithInt:5] forKey:@"fido"];
            NSNumber *n = [self valueForKey:@"fido"];
            NSLog(@"fido = %@", n);
        }
        return self;
}

(Кстати: я исправил ваш -init для реализации правильного шаблона)

Это работает, потому что KVC следует эвристике, чтобы установить или получить значение. Вызов -setValue:forKey: сначала ищет -setFoo:. Если он не найден, он ищет переменную экземпляра foo и устанавливает ее напрямую.

Обратите внимание, что если вы измените переменную экземпляра fido на _fido, набор будет работать, но valueForKey вернет 0 при вызове синтезированного метода (поскольку у меня 64-битная система, @synthesize синтезирует переменную экземпляра fido. Запутанно, я знать.).

Если бы вы изменили имя своего ivar на bar, а затем использовали @synthesize foo=bar;, код не работал во время выполнения.

Вот увидишь:

2009-10-01 08:59:58.081 dfkjdfkjfjkfd[24099:903] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<AppController 0x20000e700> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key fido.'
*** Call stack at first throw:
(
    0   CoreFoundation                      0x00007fff85b055a4 __exceptionPreprocess + 180
    1   libobjc.A.dylib                     0x00007fff85c5a0f3 objc_exception_throw + 45
    2   CoreFoundation                      0x00007fff85b5caf9 -[NSException raise] + 9
    3   Foundation                          0x00007fff814e14f5 -[NSObject(NSKeyValueCoding) setValue:forKey:] + 434
(
    0   CoreFoundation                      0x00007fff85b055a4 __exceptionPreprocess + 180
    1   libobjc.A.dylib                     0x00007fff85c5a0f3 objc_exception_throw + 45
    2   CoreFoundation                      0x00007fff85b5caf9 -[NSException raise] + 9
    3   Foundation                          0x00007fff814e14f5 -[NSObject(NSKeyValueCoding) setValue:forKey:] + 434
    4   dfkjdfkjfjkfd                       0x0000000100000d96 -[AppController init] + 130
person bbum    schedule 01.10.2009
comment
Вы также можете использовать +(BOOL) accessInstanceVariablesDirectly { return NO; } чтобы устранить это поведение KVC - person sbooth; 01.10.2009
comment
Отличный ответ, спасибо. Причина, по которой я задал этот вопрос, заключалась в том, что одним из основных моментов главы является то, что KVC использует сеттеры и геттеры. Я предполагал, что это единственный способ, которым они работают. Стиль инициализации — это стиль, который он использует в книге. Он говорит, что классы, которые он создает подклассами, никогда не возвращают nil, поэтому для простоты он опускает проверку. - person nevan king; 02.10.2009
comment
KVC будет использовать сеттер/геттер, когда он присутствует, но затем, как вы обнаружили, следует довольно... удивительный... набор эвристик. Откровенно говоря, немного непостижимо, если вы точно не знаете, что происходит. - person bbum; 02.10.2009
comment
Мех; стиль инициализации в книге неправильный. Мне нужно связаться с Аароном. - person bbum; 02.10.2009

Наличие свойства только для чтения означает, что компилятор не будет генерировать вам установщик для этого свойства. Писать на него через KVO/KVC по-прежнему разрешено.

person Eimantas    schedule 01.10.2009
comment
Это не то, что здесь происходит. - person bbum; 01.10.2009

Директивы компилятора @property и @synthesize — это просто сокращенные способы создания методов для получения и установки рассматриваемой переменной.

Созданный метод установки называется setFido:, а метод получения просто называется fido.

Когда вы указываете только чтение, я считаю, что это просто говорит компилятору не создавать метод установки, а только метод получения. Это не создает никаких препятствий на пути установки переменной другими способами.

(Надеюсь, я все правильно понял. Удачи!)

person Rob    schedule 01.10.2009