Есть ли шаблон для переопределения свойства?
Среда выполнения Objective C хранит список объявленных свойств как метаданные с объектом Class. Метаданные включают имя свойства, тип и атрибуты. Библиотека времени выполнения также предоставляет несколько функций для извлечения этой информации. Это означает, что объявленное свойство - это больше, чем пара методов доступа (getter/setter). Мой первый вопрос: зачем нам (или среде выполнения) нужны метаданные?
Как хорошо известно, объявленное свойство не может быть переопределено в подклассах (кроме readwrite и readonly). Но у меня есть сценарий, который гарантирует, что необходимо:
@interface MyClass : MySuperClass <NSCopying, NSMutableCopying>
@property (nonatomic, copy, readonly) NSString *string;
- (id)initWithString:(NSString *)aString;
@end
@interface MyMutableClass : MyClass
@property (nonatomic, strong, readwrite) NSMutableString *string;
- (id)initWithString:(NSString *)aString;
@end
Конечно, компилятор не пропустит вышеуказанный код. Мое решение состоит в том, чтобы заменить объявленное свойство парой методов доступа (в случае только для чтения, только получатель):
@interface MyClass : MySuperClass <NSCopying, NSMutableCopying> {
NSString *_string;
}
- (id)initWithString:(NSString *)aString;
- (NSString *)string;
@end
@implementation MyClass
- (id)initWithString:(NSString *)aString {
self = [super init...];
if (self) {
_string = [aString copy];
}
return self;
}
- (NSString *)string {
return _string;
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)mutableCopyWithZone:(NSZone *)zone {
return [[MyMutableClass alloc] initWithString:self.string];
}
@end
@interface MyMutableClass : MyClass
- (id)initWithString:(NSString *)aString;
- (NSMutableString *)string;
- (void)setString:(NSMutableString *)aMutableString;
- (void)didMutateString;
@end
@implementation MyMutableClass
- (id)initWithString:(NSString *)aString {
self = [super init...];
if (self) {
_string = [aString mutableCopy];
}
return self;
}
- (NSMutableString *)string {
return (NSMutableString *)_string;
}
- (void)setString:(NSMutableString *)aMutableString {
_string = aMutableString;
// Inform other parts that `string` has been changed (as a whole).
// ...
}
- (void)didMutateString {
// The content of `string` has been changed through the interface of
// NSMutableString, beneath the accessor method.
// ...
}
- (id)copyWithZone:(NSZone *)zone {
return [[MyClass alloc] initWithString:self.string];
}
@end
Имущество string
должен быть изменяемым, потому что он изменяется постепенно и потенциально часто. Я знаю ограничение, что методы с одним и тем же селектором должны использовать одни и те же типы возвращаемых значений и параметров. Но я думаю, что вышеупомянутое решение подходит как семантически, так и технически. Для семантического аспекта изменчивый объект является неизменным объектом. С технической точки зрения, компилятор кодирует все объекты как идентификаторы. Мой второй вопрос: имеет ли смысл приведенное выше решение? Или это просто странно?
Я также могу использовать гибридный подход, как показано ниже:
@interface MyClass : MySuperClass <NSCopying, NSMutableCopying> {
NSString *_string;
}
@property (nonatomic, copy, readonly) NSString *string;
- (id)initWithString:(NSString *)aString;
@end
@interface MyMutableClass: MyClass
- (id)initWithString:(NSString *)aString;
- (NSMutableString *)string;
- (void)setString:(NSMutableString *)aMutableString;
- (void)didMutateString;
@end
Тем не менее, когда я получаю доступ к свойству, используя синтаксис точки, как myMutableObject.string
компилятор предупреждает, что возвращаемый тип метода доступа не совпадает с типом объявленного свойства. Можно использовать форму сообщения как [myMutableObject string]
, Это наводит на мысль о другом аспекте, где объявленное свойство - это больше, чем пара методов доступа, то есть более статическая проверка типов, хотя здесь это нежелательно. Мой третий вопрос: распространено ли использование пары getter / setter вместо объявленного свойства, когда оно предназначено для переопределения в подклассах?
1 ответ
Мой взгляд на это будет немного другим. В случае с @interface
класса Objective-C, вы объявляете API, который этот класс использует со всеми классами, которые взаимодействуют с ним. Заменив NSString*
копировать свойство с NSMutableString*
Сильная собственность, вы создаете ситуацию, в которой могут возникнуть непредвиденные побочные эффекты.
В частности, NSString*
Ожидается, что свойство copy вернет неизменный объект, который можно было бы безопасно использовать во многих ситуациях, когда NSMutableString*
объект не будет (ключи в словарях, имена элементов в NSXMLElement
). Таким образом, вы действительно не хотите заменять их таким образом.
Если вам нужен базовый NSMutableString
Я бы предложил следующее:
- Добавить
NSMutableString*
свойство в дополнение к строковому свойству и назовите его-mutableString
- Переопределить
-setString:
метод для созданияNSMutableString
и сохранить его - Переопределить
-string
способ вернуть неизменяемую копию изменяемой строки - Тщательно оцените, можете ли вы заменить внутренний ivar на NSMutableString или нет. Это может быть проблемой, если у вас нет доступа к исходному классу, и вы не уверены, сделаны ли предположения об изменчивости строки внутри класса
Если вы сделаете это, вы будете поддерживать текущий интерфейс, не прерывая работу существующих пользователей класса, и в то же время расширяете поведение, чтобы приспособить вашу новую парадигму.
В случае переключения между изменяемым объектом и неизменным, вам действительно нужно быть осторожным, чтобы не нарушить контракт API для объекта.