Подтвердить что ты не робот

Minutia на Objective-C Категории и расширения

Я узнал что-то новое, пытаясь понять, почему мое свойство readwrite, объявленное в частной категории, не генерирует сеттер. Это потому, что моя категория была названа:

// .m
@interface MyClass (private)
@property (readwrite, copy) NSArray* myProperty;
@end

Изменение его на:

// .m
@interface MyClass ()
@property (readwrite, copy) NSArray* myProperty;
@end

и мой сеттер синтезируется. Теперь я знаю, что расширение класса - это не просто другое имя анонимной категории. Оставляя категорию неназванным, она превращается в другого зверя: тот, который теперь дает возможность реализации метода компиляции во времени, и позволяет вам добавлять ivars. Теперь я понимаю общие принципы, лежащие в основе каждого из них: Категории обычно используются для добавления методов в любой класс во время выполнения, а расширения классов обычно используются для обеспечения реализации частного API и добавления ivars. Я принимаю это.

Но есть пустяки, которые меня путают. Во-первых, на высоком уровне: зачем так дифференцироваться? Эти концепции похожи на подобные идеи, которые не могут решить, являются ли они одинаковыми или разными понятиями. Если они одинаковы, я бы ожидал, что одни и те же вещи будут доступны с помощью категории без имени, как есть, с названной категорией (которой они не являются). Если они разные, (что они), я бы ожидал большего синтаксического неравенства между ними. Кажется странным сказать: "О, кстати, для реализации расширения класса, просто напишите Категорию, но оставьте имя. Оно волшебным образом изменится".

Во-вторых, по теме принудительного выполнения компиляции: если вы не можете добавлять свойства в названную категорию, почему это делает убеждение компилятора в том, что вы это сделали? Чтобы пояснить, я проиллюстрирую на примере. Я могу объявить свойство readonly в файле заголовка:

// .h
@interface MyClass : NSObject
@property (readonly, copy) NSString* myString;
@end

Теперь я хочу перейти к файлу реализации и предоставить себе доступ к собственному доступу к ресурсу readwrite. Если я сделаю это правильно:

// .m
@interface MyClass ()
@property (readwrite, copy) NSString* myString;
@end

Я получаю предупреждение, когда не синтезирую, и когда я это делаю, я могу установить свойство, и все персиковое. Но, к сожалению, если я случайно ошибаюсь в разнице между категорией и расширением класса, и я стараюсь:

// .m
@interface MyClass (private)
@property (readwrite, copy) NSString* myString;
@end

Компилятор полностью умиротворен, думая, что свойство является readwrite. Я не получаю никаких предупреждений, и даже хорошая ошибка компиляции "Объект не может быть установлен - либо свойство readonly, либо не установлено setter" при установке myString, что я бы не объявил свойство readwrite в категории. Я просто получаю исключение "Не отвечает на селектор" во время выполнения. Если добавление ivars и свойств не поддерживается категориями (named), слишком ли сложно спросить, что компилятор играет по тем же правилам? Я пропустил какую-то грандиозную философию дизайна?

4b9b3361

Ответ 1

Расширения класса были добавлены в Objective-C 2.0 для решения двух конкретных проблем:

  • Разрешить объекту иметь интерфейс "private", который проверяется компилятором.
  • Разрешить общедоступные, конфиденциальные свойства.

Частный интерфейс

До Objective-C 2.0, если разработчик хотел иметь набор методов в Objective-C, они часто объявляли категорию "private" в файле реализации класса:

@interface MyClass (Private)
- (id)awesomePrivateMethod;
@end

Однако эти частные методы часто смешивались с блоком @implementation (а не отдельным блоком @implementation для категории Private). И почему бы нет? На самом деле это не расширение класса; они просто восполняют недостаток публичных/частных ограничений в категориях Objective-C.

Проблема заключается в том, что компиляторы Objective-C предполагают, что методы, объявленные в категории, будут реализованы в другом месте, поэтому они не проверяют, чтобы убедиться, что методы реализованы. Таким образом, разработчик может объявить awesomePrivateMethod, но не сможет его реализовать, и компилятор не предупредит их о проблеме. Это проблема, которую вы заметили: в категории вы можете объявить свойство (или метод), но не можете получить предупреждение, если вы его никогда не реализуете, - потому что компилятор ожидает, что он будет реализован "где-то" (скорее всего, в другом модуле компиляции, независимом от этого).

Введите расширения класса. Предполагается, что методы, объявленные в расширении класса, реализуются в основном блоке @implementation; если это не так, компилятор выдаст предупреждение.

Публично-читаемые, приватно-записываемые свойства

Часто бывает полезно реализовать неизменяемую структуру данных, то есть ту, в которой внешний код не может использовать установщик для изменения состояния объекта. Тем не менее, все же может быть приятно иметь доступное для записи свойство для внутреннего использования. Расширения класса позволяют: в открытом интерфейсе разработчик может объявить свойство доступным только для чтения, но затем объявить его доступным для записи в расширении класса. Для внешнего кода свойство будет доступно только для чтения, но установщик может использоваться внутри.

Итак, почему я не могу объявить записываемое свойство в категории?

Категории не могут добавлять переменные экземпляра. У сеттера часто требуется какое-то хранилище. Было решено, что разрешить категории объявлять свойство, которое, вероятно, потребовало резервного хранилища, было "Плохая вещь". Следовательно, категория не может объявить записываемое свойство.

Они похожи, но различны

Путаница заключается в том, что расширение класса - это просто "неназванная категория". Синтаксис подобен и подразумевает эту идею; Я предполагаю, что это было просто выбрано потому, что оно было знакомо программистам Objective-C и, в некотором роде, расширения классов похожи на категории. Они похожи друг на друга, поскольку обе функции позволяют добавлять методы (и свойства) к существующему классу, но они служат для разных целей и, таким образом, позволяют различное поведение.

Ответ 2

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

Категория, с другой стороны, является способом добавления методов к существующему классу во время выполнения. Это может быть, например, в отдельном комплекте, который загружается только по четвергам.

Для большей части истории Objective-C было невозможно добавить переменные экземпляра в класс во время выполнения, когда категории загружены. В последнее время это было проработано совсем недавно, но язык все еще показывает шрамы его хрупких базовых классов. Один из них заключается в том, что язык не поддерживает категории, добавляющие переменные экземпляра. Вам придется записывать геттеры и сеттеры сами, стиль старой школы.

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

Ответ 3

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

Ответ 4

Просто немного разъясняем REASON для различного поведения неназванных категорий (теперь называемых расширениями классов) и нормальных (названных) категорий.

Всё очень просто. У вас может быть много категорий, расширяющих один класс, загружаемых во время выполнения, без компилятора и компоновщика, когда-либо знающих. (рассмотрите множество красивых расширений, которые люди написали в NSObject, которые добавили его в функции post-hoc).

Теперь Objective-C не имеет понятия NAME SPACE. Поэтому, если iVars, определенный в названной категории, может создать столкновение символов во время выполнения. Если две разные категории смогут определить один и тот же

@interface myObject (extensionA) {
 NSString *myPrivateName;
}
@end
@interface myObject (extensionB) {
 NSString *myPrivateName;
}
@end

то, по крайней мере, во время выполнения произойдет переполнение памяти.

В противоречии расширения класса не имеют NAME, и, следовательно, может быть только один. Вот почему вы можете определить iVars. Они гарантированы быть уникальными.

Что касается ошибок и предупреждений компилятора, связанных с категориями и расширениями классов + ivars и определениями свойств, я должен согласиться, что они не очень полезны, и я потратил слишком много времени, пытаясь понять, почему вещи компилируются или нет, и как они (если они работают) после компиляции.