Подтвердить что ты не робот

Переопределение свойства readonly в подклассе

Существует класс, который выглядит следующим образом (я сокращаю импорт для краткости):

Base.h:

@interface Base : NSObject
@property (strong, readonly) NSString *something;
- (id)initWithSomething:(NSString *)something;

@end

Base.m:

@implementation Base
- (id)initWithSomething:(NSString *)something {
    self = [super init];
    if (self) _something = something;
    return self;
}
@end

Как вы видите, свойство "что-то" доступно только для чтения. Теперь я хочу создать подкласс, который переопределяет это свойство для записи:

Sub.h:

@interface Sub : Base
@property (strong) NSString *something;
@end

Sub.m:

@implementation Sub
@end

И код:

main.c:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Sub *o = [Sub new];
        o.something = @"foo";
        NSLog(@"%@", o.something);
    }
    return 0;
}

Этот код приводит к:

    2013-09-07 13:58:36.970 ClilTest[3094:303] *** Terminating app due to uncaught
    exception 'NSInvalidArgumentException', reason: '-[Sub setSomething:]: unrecognized
    selector sent to instance 0x100109ff0'

Почему? Почему он не находит setSelector?

Когда я делаю это в подклассе, вместо этого:

Sub.m:

@implementation Sub
@synthesize something = _something;
@end

все работает. Означает ли это, что свойство подкласса не синтезируется по умолчанию, хотя оно определено как @property в @interface? Компиляция каким-то образом "видит" автоматически созданный геттер с базы и не генерирует сеттер? И почему, я думаю, сеттер должен быть сгенерирован, поскольку он еще не существует. Я использую Xcode 4.6.2, а проект - Cli Tool (тип Foundation), но то же самое происходит в моем фактическом проекте, который является приложением для iPhone.

Фон: у меня есть тяжелый объект (экземпляр Base), который требует подключения Bluetooth к некоторому оборудованию, и я должен создать контроллер вида для некоторых функций. Для легкого тестирования я не хочу подключаться к BT (на самом деле, мне нужно было бы физическое устройство и проверить код на нем), я бы хотел проверить его на симуляторе. Я пришел к выводу, что я просто создаю подкласс (Sub), который заглушает несколько методов/свойств и использует его вместо этого, и когда код готов, я просто удалю код для подкласса, заменим его экземпляр на правильный, протестировать устройство, зафиксировать и нажать. Он действительно работает отлично, за исключением странной вещи с @property выше.

Может ли кто-нибудь сказать мне, что происходит с переопределением свойства?

4b9b3361

Ответ 1

Для свойства readonly синтезируется только метод getter, но нет метода setter.

И при компиляции подкласса компилятор не знает, как реализуется свойство в базовом классе (это может быть пользовательский getter вместо переменной экземпляра резервной копии). Поэтому он не может просто создать метод setter в подклассе.


Если вы хотите иметь доступ на запись к той же переменной экземпляра из подкласса, вы должны объявить его как @protected в базовом классе (чтобы он был доступен в подклассе), повторно объявите свойство  как чтение-запись в подклассе, и предоставить метод сеттера:

Base.h:

@interface Base : NSObject {
@protected
    NSString *_something;
}
@property (strong, readonly) NSString *something;
- (id)initWithSomething:(NSString *)something;
@end

Sub.h:

@interface Sub : Base
@property (strong, readwrite) NSString *something;
@end

Sub.m:

@implementation Sub
-(void)setSomething:(NSString *)something
{
    _something = something;
}
@end

Ваше решение

@synthesize something = _something;

генерирует метод getter и setter в подклассе, используя отдельный экземпляр переменная _something в подклассе (которая отличается от _something в базовом классе).

Это также работает, вы просто должны знать, что self.something относится к различные переменные экземпляра в базовом классе и в подклассе. Сделать это более очевидно, вы могли бы использовать другую переменную экземпляра в подклассе:

@synthesize something = _somethingElse;

Ответ 2

Данный ответ работает отлично. Это альтернативный ответ, что, очевидно, Apple любит немного больше.

Вы можете определить личное расширение вашего класса, файл Base+Protected.h, который должен быть включен в Base.m и Sub.m.

Затем в этом новом файле вы переопределяете свойство как readwrite.

@interface Base ()
@property (strong, readwrite) NSString *something;
@end

Эта альтернатива позволяет использовать аксессор self.something, а не ivar _something.

Примечание: вам все равно нужно сохранить определение something в Base.h как есть.

Ответ 3

Я предполагаю, что поддерживающие переменные одинаковы, когда свойство не синтезируется в подклассе. Поэтому во время выполнения программа пытается вызвать setSomething в суперклассе. Но так как он не существует, возникает исключение.