Создание абстрактного класса в Objective-C - PullRequest
499 голосов
/ 23 июня 2009

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

Если нет, как близко к абстрактному классу я могу получить в Objective-C?

Ответы [ 21 ]

626 голосов
/ 23 июня 2009

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

[NSException raise:NSInternalInconsistencyException 
            format:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)];

Если ваш метод возвращает значение, его немного проще использовать

@throw [NSException exceptionWithName:NSInternalInconsistencyException
                               reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]
                             userInfo:nil];

так как тогда вам не нужно добавлять оператор возврата из метода.

Если абстрактный класс действительно является интерфейсом (т. Е. Не имеет конкретных реализаций метода), использование протокола Objective-C является более подходящим вариантом.

267 голосов
/ 23 июня 2009

Нет, в Objective-C нет способа создать абстрактный класс.

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

Например:

- (id)someMethod:(SomeObject*)blah
{
     [self doesNotRecognizeSelector:_cmd];
     return nil;
}

Вы также можете сделать это для init.

60 голосов
/ 23 июня 2011

Просто перефразирую ответ @Barry Wark выше (и обновляю для iOS 4.3) и оставлю это для моей собственной справки:

#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]
#define methodNotImplemented() mustOverride()

тогда в ваших методах вы можете использовать это

- (void) someMethod {
     mustOverride(); // or methodNotImplemented(), same thing
}



Примечания: Не уверен, что делать макрос похожим на функцию C - хорошая идея или нет, но я буду держать его до тех пор, пока не изучу обратное. Я думаю, что более правильно использовать NSInvalidArgumentException (а не NSInternalInconsistencyException), поскольку именно это система времени выполнения выдает в ответ на вызываемый doesNotRecognizeSelector (см. NSObject docs).

41 голосов
/ 21 июня 2012

Решение, которое я придумал:

  1. Создайте протокол для всего, что вы хотите в своем "абстрактном" классе
  2. Создайте базовый класс (или, возможно, назовите его абстрактным), который реализует протокол. Для всех методов, которые вы хотите, чтобы "абстрактные" реализовали их в файле .m, но не в файле .h.
  3. Пусть ваш дочерний класс наследует от базового класса И реализует протокол.

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

Это не так лаконично, как в Java, но вы получите предупреждение от нужного компилятора.

34 голосов
/ 23 июня 2009

Из списка рассылки Omni Group :

Objective-C не имеет абстрактной конструкции компилятора, как Java в на этот раз.

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

- (id)someMethod:(SomeObject*)blah
{
     [self doesNotRecognizeSelector:_cmd];
     return nil;
}

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

- (id)init
{
     [self doesNotRecognizeSelector:_cmd];
     [self release];
     return nil;
}
21 голосов
/ 23 июня 2009

Вместо того, чтобы пытаться создать абстрактный базовый класс, рассмотрите возможность использования протокола (аналогичного интерфейсу Java). Это позволяет вам определить набор методов, а затем принять все объекты, которые соответствуют протоколу, и реализовать методы. Например, я могу определить протокол операции, а затем иметь функцию, подобную этой:

- (void)performOperation:(id<Operation>)op
{
   // do something with operation
}

Где op может быть любым объектом, реализующим протокол операции.

Если вам нужен ваш абстрактный базовый класс, чтобы делать больше, чем просто определять методы, вы можете создать обычный класс Objective-C и предотвратить его создание. Просто переопределите функцию инициализации - (id) и сделайте так, чтобы она возвращала nil или assert (false). Это не очень чистое решение, но, поскольку Objective-C является полностью динамическим, в действительности нет прямого эквивалента абстрактному базовому классу.

19 голосов
/ 23 июля 2013

Эта тема довольно старая, и большая часть того, чем я хочу поделиться, уже здесь.

Тем не менее, мой любимый метод не упомянут, и у AFAIK нет никакой нативной поддержки в текущем Clang, так что я иду…

Во-первых, и прежде всего (как уже отмечали другие) абстрактные классы являются чем-то необычным в Objective-C - вместо этого мы обычно используем композицию (иногда посредством делегирования). Вероятно, это причина того, что такая функция еще не существует в языке / компиляторе - кроме свойств @dynamic, которые IIRC были добавлены в ObjC 2.0, сопровождающую введение CoreData.

Но учитывая, что (после тщательной оценки вашей ситуации!) Вы пришли к выводу, что делегирование (или состав в целом) не очень подходит для решения вашей проблемы, вот как I делает это :

  1. Реализуйте каждый абстрактный метод в базовом классе.
  2. Сделать эту реализацию [self doesNotRecognizeSelector:_cmd];
  3. … сопровождаемый __builtin_unreachable();, чтобы заставить замолчать предупреждение, которое вы получите для не пустых методов, сообщая вам «контроль достигнут конец не пустой функции без возврата».
  4. Либо объедините шаги 2. и 3. в макросе, либо аннотируйте -[NSObject doesNotRecognizeSelector:], используя __attribute__((__noreturn__)) в категории без реализации , чтобы не заменить первоначальную реализацию этого метода, и включите заголовок для этой категории в PCH вашего проекта.

Я лично предпочитаю макро-версию, так как она позволяет максимально уменьшить шаблон.

Вот оно:

// Definition:
#define D12_ABSTRACT_METHOD {\
 [self doesNotRecognizeSelector:_cmd]; \
 __builtin_unreachable(); \
}

// Usage (assuming we were Apple, implementing the abstract base class NSString):
@implementation NSString

#pragma mark - Abstract Primitives
- (unichar)characterAtIndex:(NSUInteger)index D12_ABSTRACT_METHOD
- (NSUInteger)length D12_ABSTRACT_METHOD
- (void)getCharacters:(unichar *)buffer range:(NSRange)aRange D12_ABSTRACT_METHOD

#pragma mark - Concrete Methods
- (NSString *)substringWithRange:(NSRange)aRange
{
    if (aRange.location + aRange.length >= [self length])
        [NSException raise:NSInvalidArgumentException format:@"Range %@ exceeds the length of %@ (%lu)", NSStringFromRange(aRange), [super description], (unsigned long)[self length]];

    unichar *buffer = (unichar *)malloc(aRange.length * sizeof(unichar));
    [self getCharacters:buffer range:aRange];

    return [[[NSString alloc] initWithCharactersNoCopy:buffer length:aRange.length freeWhenDone:YES] autorelease];
}
// and so forth…

@end

Как вы можете видеть, макрос обеспечивает полную реализацию абстрактных методов, сводя необходимое количество шаблонов к абсолютному минимуму.

Еще лучшим вариантом было бы лоббировать команду Clang для предоставления атрибута компилятора для этого случая через запросы функций. (Лучше, потому что это также позволило бы диагностику во время компиляции для тех сценариев, где вы подкласс, например, NSIncrementalStore.)

Почему я выбираю этот метод

  1. Это сделано эффективно и несколько удобно.
  2. Это довольно легко понять. (Хорошо, что __builtin_unreachable() может удивить людей, но и это достаточно легко понять.)
  3. Его нельзя удалить в сборках выпуска без генерации других предупреждений компилятора или ошибок - в отличие от подхода, основанного на одном из макросов утверждений.

Этот последний пункт требует некоторого объяснения, я думаю:

Некоторые (большинство?) Люди отбрасывают утверждения в сборках релизов. (Я не согласен с этой привычкой, но это другая история ...) Если не реализовать требуемый метод - однако - это плохо , ужасно , неправильно и в основном конец вселенной для вашей программы. Ваша программа не может работать правильно в этом отношении, потому что она не определена, а неопределенное поведение - худшая вещь в мире. Следовательно, возможность лишить эту диагностику без создания новой диагностики будет совершенно неприемлемо.

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

12 голосов
/ 04 ноября 2010

Использование @property и @dynamic также может работать. Если вы объявите динамическое свойство и не дадите подходящую реализацию метода, все по-прежнему будет компилироваться без предупреждений, и вы получите ошибку unrecognized selector во время выполнения, если попытаетесь получить к нему доступ. По сути, это то же самое, что и вызов [self doesNotRecognizeSelector:_cmd], но с гораздо меньшим набором текста.

7 голосов
/ 27 марта 2014

Ответ на вопрос разбросан в комментариях под уже предоставленными ответами. Итак, я просто суммирую и упрощаю здесь.

Опция 1: протоколы

Если вы хотите создать абстрактный класс без реализации, используйте «Протоколы». Классы, наследующие протокол, обязаны реализовывать методы в протоколе.

@protocol ProtocolName
// list of methods and properties
@end

Option2: шаблонный шаблон Pattern

Если вы хотите создать абстрактный класс с частичной реализацией, такой как «Шаблон метода шаблона», то это решение. Objective-C - Шаблон методов шаблона?

7 голосов
/ 06 ноября 2012

В Xcode (с использованием clang и т. Д.) Я хотел бы использовать __attribute__((unavailable(...))) для обозначения абстрактных классов, чтобы вы получили сообщение об ошибке / предупреждение, если попытаетесь его использовать.

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

Пример

В базовом классе @interface отметьте «абстрактные» методы:

- (void)myAbstractMethod:(id)param1 __attribute__((unavailable("You should always override this")));

Продолжая этот шаг, я создаю макрос:

#define UnavailableMacro(msg) __attribute__((unavailable(msg)))

Это позволяет вам сделать это:

- (void)myAbstractMethod:(id)param1 UnavailableMacro(@"You should always override this");

Как я уже сказал, это не настоящая защита компилятора, но она примерно так же хороша, как и в языке, который не поддерживает абстрактные методы.

...