Я изначально программист на Java, который сейчас работает с Objective-C. Я хотел бы создать абстрактный класс, но это не представляется возможным в Objective-C. Это возможно?
Если нет, то как близко к абстрактному классу я могу получить в Objective-C?
objective-c
abstract-class
Джонатан Арбогаст
источник
источник
Ответы:
Как правило, класс Objective-C является абстрактным только по соглашению - если автор документирует класс как абстрактный, просто не используйте его, не разделив его на подклассы. Однако не существует принудительного исполнения во время компиляции, которое предотвращает создание экземпляров абстрактного класса. Фактически, ничто не мешает пользователю предоставлять реализации абстрактных методов через категорию (т.е. во время выполнения). Вы можете заставить пользователя по крайней мере переопределить определенные методы, вызвав исключение в реализации этих методов в вашем абстрактном классе:
Если ваш метод возвращает значение, это немного проще в использовании
тогда вам не нужно добавлять оператор возврата из метода.
Если абстрактный класс действительно является интерфейсом (т.е. не имеет конкретных реализаций метода), использование протокола Objective-C является более подходящим вариантом.
источник
@protocol
определении, но вы не можете определить методы там.+ (instancetype)exceptionForCallingAbstractMethod:(SEL)selector
это работает очень хорошо.doesNotRecognizeSelector
.Нет, в Objective-C нет способа создать абстрактный класс.
Вы можете смоделировать абстрактный класс - сделав вызов методов / селекторов didNotRecognizeSelector: и, следовательно, вызвать исключение, делающее класс непригодным для использования.
Например:
Вы также можете сделать это для init.
источник
NSObject
справочнике предлагается использовать его там, где вы НЕ хотите наследовать метод, а не применять переопределение метода. Хотя, возможно, это одно и то же :)Просто перефразирую ответ @Barry Wark выше (и обновление для iOS 4.3) и оставлю это для моей собственной справки:
тогда в ваших методах вы можете использовать это
Примечания: Не уверен, что делать макрос похожим на функцию C - это хорошая идея или нет, но я буду держать его до тех пор, пока не изучу обратное. Я думаю, что более правильно использовать
NSInvalidArgumentException
(а неNSInternalInconsistencyException
), поскольку именно это выдает система времени выполнения в ответ наdoesNotRecognizeSelector
вызов (см.NSObject
Документацию).источник
universe.thing
но он расширяется до[Universe universe].thing
. Большое удовольствие, спасая тысячи букв кода ...#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]
.__PRETTY_FUNCTION__
повсеместно, через макрос DLog (...), как предлагается здесь: stackoverflow.com/a/969291/38557 .#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[BaseDynamicUIViewController localizeUI] must be overridden in a subclass/category'
в случае, если я предложил вам также получить,HomeViewController - method not implemented
где HomeViewController унаследован от Base - это даст больше информацииРешение, которое я придумал:
Таким образом, компилятор выдаст вам предупреждение для любого метода в протоколе, который не реализован вашим дочерним классом.
Это не так лаконично, как в Java, но вы получите предупреждение от нужного компилятора.
источник
NSObject
). Если вы поместите протокол и объявление базового класса в один заголовочный файл, он будет почти неотличим от абстрактного класса.Из списка рассылки Omni Group :
В настоящее время Objective-C не имеет абстрактной конструкции компилятора, как Java.
Поэтому все, что вам нужно сделать, это определить абстрактный класс как любой другой нормальный класс и реализовать заглушки методов для абстрактных методов, которые либо пусты, либо сообщают о неподдержке селектора. Например...
Я также делаю следующее, чтобы предотвратить инициализацию абстрактного класса через инициализатор по умолчанию.
источник
Вместо того, чтобы пытаться создать абстрактный базовый класс, рассмотрите возможность использования протокола (аналогичного интерфейсу Java). Это позволяет вам определить набор методов, а затем принять все объекты, которые соответствуют протоколу, и реализовать методы. Например, я могу определить протокол операции, а затем иметь такую функцию:
Где op может быть любым объектом, реализующим протокол операции.
Если вам нужен ваш абстрактный базовый класс, чтобы делать больше, чем просто определять методы, вы можете создать обычный класс Objective-C и предотвратить его создание. Просто переопределите функцию инициализации - (id) и сделайте так, чтобы она возвращала nil или assert (false). Это не очень чистое решение, но, поскольку Objective-C является полностью динамическим, в действительности нет прямого эквивалента абстрактному базовому классу.
источник
Эта ветка довольно старая, и большая часть того, чем я хочу поделиться, уже здесь.
Тем не менее, мой любимый метод не упоминается, и AFAIK нет родной поддержки в текущем Clang, так что здесь я иду ...
Во-первых, и прежде всего (как уже отмечали другие) абстрактные классы являются чем-то необычным в Objective-C - мы обычно используем вместо этого композицию (иногда посредством делегирования). Вероятно, это причина того, что такая функция еще не существует в языке / компиляторе - кроме
@dynamic
свойств, которые IIRC были добавлены в ObjC 2.0, сопровождающем введение CoreData.Но учитывая, что (после тщательной оценки вашей ситуации!) Вы пришли к выводу, что делегирование (или состав в целом) не очень подходит для решения вашей проблемы, вот как я это делаю:
[self doesNotRecognizeSelector:_cmd];
...__builtin_unreachable();
чтобы заставить замолчать предупреждение, которое вы получите для не пустых методов, сообщая вам «контроль достигнут недействительной функции без возврата».-[NSObject doesNotRecognizeSelector:]
использование__attribute__((__noreturn__))
в категории без реализации, чтобы не заменить первоначальную реализацию этого метода, и включите заголовок для этой категории в PCH вашего проекта.Я лично предпочитаю макро-версию, так как она позволяет максимально уменьшить шаблон.
Вот:
Как видите, макрос обеспечивает полную реализацию абстрактных методов, сводя необходимое количество шаблонов к абсолютному минимуму.
Еще лучшим вариантом было бы лоббировать команду Clang предоставить атрибут компилятора для этого случая, с помощью художественных запросов. (Лучше, потому что это также позволило бы диагностику во время компиляции для тех сценариев, где вы подкласс, например, NSIncrementalStore.)
Почему я выбираю этот метод
__builtin_unreachable()
может удивить людей, но и это достаточно легко понять.)Этот последний пункт требует пояснения, я думаю:
Некоторые (большинство?) Люди отбрасывают утверждения в сборках релизов. (Я не согласен с этой привычкой, но это другая история ...) Неспособность реализовать требуемый метод - однако - это плохо , ужасно , неправильно и, по сути, конец вселенной для вашей программы. Ваша программа не может работать правильно в этом отношении, потому что она не определена, а неопределенное поведение - худшая вещь в мире. Следовательно, возможность лишить эту диагностику без создания новой диагностики будет совершенно неприемлемо.
Достаточно плохо, что вы не можете получить надлежащую диагностику во время компиляции для таких ошибок программиста, и вынуждены прибегать к обнаружению во время выполнения для них, но если вы можете обмазать это в сборках релиза, зачем пытаться иметь абстрактный класс в первое место?
источник
__builtin_unreachable();
драгоценный камень делает эту работу отлично. Макрос делает самодокументирование, и поведение соответствует тому, что происходит, если вы вызываете отсутствующий метод объекта.Использование
@property
и@dynamic
может также работать. Если вы объявите динамическое свойство и не дадите подходящую реализацию метода, все будет компилироваться без предупреждений, и вы получитеunrecognized selector
ошибку во время выполнения, если попытаетесь получить к нему доступ. По сути, это то же самое, что и вызов[self doesNotRecognizeSelector:_cmd]
, но с гораздо меньшим набором текста.источник
В Xcode (используя clang и т. Д.) Мне нравится использовать
__attribute__((unavailable(...)))
теги для абстрактных классов, чтобы вы получили сообщение об ошибке / предупреждение, если попытаетесь его использовать.Это обеспечивает некоторую защиту от случайного использования метода.
пример
В
@interface
теге базового класса «абстрактные» методы:Продолжая этот шаг, я создаю макрос:
Это позволяет вам сделать это:
Как я уже сказал, это не настоящая защита компилятора, но она примерно так же хороша, как и в языке, который не поддерживает абстрактные методы.
источник
-init
метод в вашем подклассе.Ответ на вопрос разбросан в комментариях под уже предоставленными ответами. Итак, я просто суммирую и упрощаю здесь.
Вариант 1: протоколы
Если вы хотите создать абстрактный класс без реализации, используйте «Протоколы». Классы, наследующие протокол, обязаны реализовывать методы в протоколе.
Вариант 2: Шаблон Метод Шаблон
Если вы хотите создать абстрактный класс с частичной реализацией, такой как «Шаблон шаблона шаблона», то это решение. Objective-C - Шаблон методов шаблонов?
источник
Еще одна альтернатива
Просто проверьте класс в классе Abstract и Assert или Exception, как вам нравится.
Это устраняет необходимость переопределения
init
источник
(больше связанного предложения)
Я хотел иметь способ дать программисту знать «не звонить от ребенка» и полностью переопределить (в моем случае все еще предлагать некоторую функциональность по умолчанию от имени родителя, когда он не расширен):
Преимущество состоит в том, что программист увидит «переопределение» в объявлении и будет знать, что он не должен вызывать
[super ..]
.Конечно, для этого некрасиво определять отдельные типы возвращаемых данных, но это служит достаточно хорошим визуальным советом, и вы не можете легко использовать часть «override_» в определении подкласса.
Конечно, класс все еще может иметь реализацию по умолчанию, когда расширение является необязательным. Но, как говорят другие ответы, при необходимости реализуйте исключение времени выполнения, как для абстрактных (виртуальных) классов.
Было бы хорошо иметь встроенные подсказки компилятора, подобные этой, даже подсказки, когда лучше предварительно / опубликовать вызов агрегата супер, вместо того, чтобы копаться в комментариях / документации или ... предполагать.
источник
Если вы привыкли к тому, что компилятор отлавливает нарушения абстрактных реализаций в других языках, то поведение Objective-C разочаровывает.
Как язык позднего связывания, ясно, что Objective-C не может принимать статические решения о том, действительно ли класс является абстрактным или нет (вы можете добавлять функции во время выполнения ...), но для типичных случаев использования это кажется недостатком. Я бы предпочел, чтобы компилятор предотвращал создание экземпляров абстрактных классов, а не выдавал ошибку во время выполнения.
Вот образец, который мы используем, чтобы получить этот тип статической проверки, используя несколько методов, чтобы скрыть инициализаторы:
источник
Вероятно, такого рода ситуации должны происходить только во время разработки, поэтому это может сработать:
источник
Вы можете использовать метод, предложенный @Yar (с некоторыми изменениями):
Здесь вы получите сообщение типа:
Или утверждение:
В этом случае вы получите:
Также вы можете использовать протоколы и другие решения - но это одно из самых простых.
источник
Какао не дает ничего, что называется абстрактным. Мы можем создать реферат класса, который проверяется только во время выполнения, а во время компиляции это не проверяется.
источник
Я обычно просто отключаю метод init в классе, который хочу абстрагировать:
Это сгенерирует ошибку во время компиляции всякий раз, когда вы вызываете init для этого класса. Затем я использую методы класса для всего остального.
Objective-C не имеет встроенного способа объявления абстрактных классов.
источник
Немного изменив то, что предложил @redfood, применив комментарий @ dotToString, вы фактически получаете решение, принятое IGListKit из Instagram .
AbstractClass
должен вводиться или выводиться каким-либо методом, введите его какAbstractClass<Protocol>
взамен.Поскольку
AbstractClass
не реализуетProtocol
, единственный способ получитьAbstractClass<Protocol>
экземпляр - это создать подклассы. ПосколькуAbstractClass
одно только не может использоваться нигде в проекте, оно становится абстрактным.Конечно, это не мешает сторонним разработчикам добавлять новые методы, ссылающиеся просто на них
AbstractClass
, что в конечном итоге позволило бы создать экземпляр (больше не) абстрактного класса.Пример из реальной жизни : IGListKit имеет базовый класс,
IGListSectionController
который не реализует протоколIGListSectionType
, однако каждый метод, которому требуется экземпляр этого класса, фактически запрашивает типIGListSectionController<IGListSectionType>
. Поэтому нет никакого способа использовать объект типаIGListSectionController
для чего-либо полезного в их структуре.источник
На самом деле Objective-C не имеет абстрактных классов, но вы можете использовать протоколы для достижения того же эффекта. Вот образец:
CustomProtocol.h
TestProtocol.h
TestProtocol.m
источник
Простой пример создания абстрактного класса
источник
Вы не можете просто создать делегата?
Делегат похож на абстрактный базовый класс в том смысле, что вы говорите, какие функции должны быть определены, но на самом деле вы их не определяете.
Затем всякий раз, когда вы реализуете свой делегат (т.е. абстрактный класс), компилятор предупреждает вас о том, для каких необязательных и обязательных функций вам нужно определить поведение.
Это звучит как абстрактный базовый класс для меня.
источник