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

Среда выполнения 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 для объекта.

Другие вопросы по тегам