Переопределение свойства 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
выше.
Может ли кто-нибудь сказать мне, что происходит с переопределением свойства?
Ответы
Ответ 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 в суперклассе. Но так как он не существует, возникает исключение.