Why does a readonly property still allow writing with KVC

Asked
Viewd3558

5

I'm working through the "Key Value Coding" chapter in "Programming for Mac OS X". I've built an interface with a slider and a label, both bound to fido, an int. If I set the property for fido to readonly, moving the slider still causes the label to change it's value. I had assumed that I'd get some sort of error for this. If the property is readonly, how come the slider can still write to the property? I thought that it would have no setters created, and KVC wouldn't work. Thanks.

Here's the code I'm using:

#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

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

3 ответов

16

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
 
  • Great answer, thanks. The reason I was asking this question was that one of the main points of the chapter is that KVC uses the setters and getters. I’d assumed that was the only way they worked. The init style is the style he uses in the book. He says that the classes he subclasses never return nil, so he leaves out the check for simplicity.

    nevan king02 октября 2009, 03:28
  • Meh; the init style in the book is wrong. I’ll have to contact Aaron.

    bbum02 октября 2009, 18:17
  • You can also use +(BOOL) accessInstanceVariablesDirectly { return NO; } to eliminate this KVC behavior

    sbooth01 октября 2009, 17:03
  • KVC будет использовать сеттер / получатель, если он присутствует, но затем, как вы обнаружили, он следует довольно… удивительным… набором эвристик. Честно говоря, немного непостижимо, если вы точно не знаете, что происходит.

    bbum02 октября 2009, 04:56
1

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

  • That isn’t what is happening here.

    bbum01 октября 2009, 15:53
1

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

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

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

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