Есть ли шаблон для переопределения свойства?

Среда выполнения 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]. Это наводит на мысль о другом аспекте, когда объявленное свойство представляет собой нечто большее, чем пара методов доступа, то есть более статическую проверку типа, хотя здесь это нежелательно. Мой третий вопрос: Распространено ли использование пары геттер / сеттер вместо объявленного свойства, когда его предполагается переопределить в подклассах?


person Ding Curie    schedule 29.05.2012    source источник


Ответы (1)


Я бы немного по-другому смотрел на это. В случае @interface класса Objective-C вы объявляете API, который использует этот класс, со всеми классами, которые с ним взаимодействуют. Заменяя свойство NSString* copy на NSMutableString* strong, вы создаете ситуацию, в которой могут возникнуть неожиданные побочные эффекты.

В частности, ожидается, что свойство копирования NSString* вернет неизменяемый объект, который можно было бы безопасно использовать во многих ситуациях, в отличие от объекта NSMutableString* (ключи в словарях, имена элементов в NSXMLElement). Таким образом, вы действительно не хотите заменять их таким образом.

Если вам нужен базовый NSMutableString, я бы предложил следующее:

  • Добавьте свойство NSMutableString* в дополнение к свойству строки и назовите его -mutableString
  • Переопределите метод -setString:, чтобы создать NSMutableString и сохранить его
  • Переопределите метод -string, чтобы он возвращал неизменяемую копию вашей изменяемой строки
  • Тщательно оцените, можете ли вы заменить внутренний ivar на NSMutableString или нет. Это может быть проблемой, если у вас нет доступа к исходному классу и вы не уверены, сделаны ли предположения об изменчивости строки внутри класса.

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

В случае переключения между изменяемым объектом и неизменяемым вам действительно нужно быть осторожным, чтобы не нарушить контракт API для объекта.

person gaige    schedule 29.05.2012
comment
Хороший ответ. Я думаю, вы на месте, способ сделать это - переопределить установщик и получатель для исходного свойства. Разумно действовать с осторожностью, поскольку вы нарушаете контракт. - person William Denniss; 18.07.2013