Среда выполнения Objective-C хранит список объявленных свойств в виде метаданных с объектом Class. Мета-данные включают имя свойства, тип и атрибуты. Библиотека времени выполнения также предоставляет несколько функций для получения этой информации. Это означает, что объявленное свойство - это больше, чем пара методов доступа (геттер / сеттер). Мой первый вопрос: Зачем нам (или среде выполнения) нужны метаданные?
Как хорошо известно, объявленное свойство нельзя переопределить в подклассах (кроме readwrite vs. 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
должно быть изменяемым, поскольку оно изменяется постепенно и потенциально часто. Я знаю ограничение, заключающееся в том, что методы с одним и тем же селектором должны использовать одни и те же возвращаемые значения и типы параметров. Но я считаю, что приведенное выше решение подходит как семантически, так и технически. В семантическом аспекте изменяемый объект является неизменяемым объектом. С технической точки зрения компилятор кодирует все объекты как id. Мой второй вопрос: Имеет ли смысл приведенное выше решение? Или это просто странно?
Я также могу использовать гибридный подход, а именно:
@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]
. Это наводит на мысль о другом аспекте, когда объявленное свойство представляет собой нечто большее, чем пара методов доступа, то есть более статическую проверку типа, хотя здесь это нежелательно. Мой третий вопрос: Распространено ли использование пары геттер / сеттер вместо объявленного свойства, когда его предполагается переопределить в подклассах?