Создание абстрактного класса в Objective-C

507

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

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

Джонатан Арбогаст
источник
18
Ответы ниже отличные. Я считаю, что проблема абстрактных классов имеет тангенциальное отношение к закрытым методам - ​​оба являются методами для ограничения возможностей клиентского кода, и ни один из них не существует в Objective-C. Я думаю, что это помогает понять, что менталитет самого языка в корне отличается от Java. Смотрите мой ответ на: stackoverflow.com/questions/1020070/#1020330
Куинн Тейлор
Спасибо за информацию о менталитете сообщества Objective-C в отличие от других языков. Это действительно решает ряд вопросов, которые у меня были (например, почему нет простого механизма для частных методов и т. Д.).
Джонатан Арбогаст
1
так что взгляните на сайт CocoaDev, который дает сравнение java cocoadev.com/index.pl?AbstractSuperClass
2
Хотя Барри упоминает это как запоздалую мысль (простите, если я читаю это неправильно), я думаю, что вы ищете Протокол в Задаче C. См., Например, Что такое Протокол? ,
jww

Ответы:

633

Как правило, класс 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 является более подходящим вариантом.

Барри Уарк
источник
18
Я думаю, что наиболее подходящей частью ответа было упоминание, что вы могли бы вместо этого использовать @protocol для определения методов.
Чед Стюарт
13
Чтобы уточнить: вы можете объявить методы в @protocolопределении, но вы не можете определить методы там.
Ричард
С методом категорий на NSException + (instancetype)exceptionForCallingAbstractMethod:(SEL)selectorэто работает очень хорошо.
Патрик Пейнаппель
Это сработало для меня, так как, IMHO, создание исключения более очевидно для других разработчиков, что это желаемое поведение в большей степени, чем doesNotRecognizeSelector.
Крис
Используйте протоколы (для полностью абстрактного класса) или шаблонный шаблонный метод, где абстрактный класс имеет частичную логику реализации / потока, как указано здесь stackoverflow.com/questions/8146439/… . Смотрите мой ответ ниже.
Hadaytullah
267

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

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

Например:

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

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

Grouchal
источник
5
@ Чак, я не понизил голосование, но в NSObjectсправочнике предлагается использовать его там, где вы НЕ хотите наследовать метод, а не применять переопределение метода. Хотя, возможно, это одно и то же :)
Дэн Розенстарк,
Почему вы не хотите просто использовать протокол в этом случае? Мне это приятно знать только для заглушек методов, но не для целого абстрактного класса. Вариант использования для этого кажется ограниченным.
Левигес
Я не отрицал это, но предположение, что вы должны вызвать исключение в методе init, является наиболее вероятной причиной этого. Наиболее распространенный формат для подкласса запускает собственный метод init, вызывая self = [super init], что послушно вызывает исключение. Этот формат хорошо работает для большинства методов, но я бы никогда не сделал это во всем, что подкласс мог бы назвать супер реализацией.
Ксоно
5
Вы абсолютно можете создавать абстрактные классы в Objective-C, и это довольно часто. Есть ряд классов Apple Framework, которые делают это. Абстрактные классы Apple выдают конкретные исключения (NSInvalidArgumentException), часто вызывая NSInvalidAbstractInvocation (). Вызов абстрактного метода является ошибкой программирования, поэтому он вызывает исключение. Абстрактные фабрики обычно реализуются как кластеры классов.
ужасно
2
@quellish: Как вы сказали: вызов абстрактного метода - ошибка программирования. Это следует рассматривать как таковое, а не полагаться на отчеты об ошибках во время выполнения (NSException). Я думаю, что это самая большая проблема для разработчиков, пришедших из других языков, где абстрактное означает «не может создать экземпляр объекта этого типа» в Obj-C, это означает «что-то пойдет не так во время выполнения, когда вы создадите экземпляр этого класса».
Cross_
60

Просто перефразирую ответ @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Документацию).

Дэн Розенстарк
источник
1
Конечно же, @TomA, я надеюсь, что это даст вам идеи для другого кода, который вы можете макросировать. Мой самый используемый макрос - это простая ссылка на одиночный код: код говорит, 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].
noamtm
1
@ Яр: я так не думаю. Мы используем __PRETTY_FUNCTION__повсеместно, через макрос DLog (...), как предлагается здесь: stackoverflow.com/a/969291/38557 .
noamtm
1
для получения более подробной информации -#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()
ГБК
1
если вы добавляете базовый класс, а затем наследуете этот класс, например, 10 раз, и забыли реализовать это в одном из классов, вы получите сообщение с именем базового класса, не унаследованного, как, например, Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[BaseDynamicUIViewController localizeUI] must be overridden in a subclass/category'в случае, если я предложил вам также получить, HomeViewController - method not implementedгде HomeViewController унаследован от Base - это даст больше информации
gbk
42

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

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

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

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

redfood
источник
2
+1 Это действительно решение, которое ближе всего подходит к абстрактному классу в Java. Я сам использовал этот подход, и он прекрасно работает. Можно даже назвать протокол так же, как базовый класс (так же, как Apple NSObject). Если вы поместите протокол и объявление базового класса в один заголовочный файл, он будет почти неотличим от абстрактного класса.
codingFriend1
Ах, но мой абстрактный класс реализует часть протокола, остальное реализуется подклассами.
Роджер К.С. Вернерссон
1
вы могли бы иметь только дочерние классы, реализующие протокол, и оставить методы суперкласса все вместе вместо пустых. затем имеют свойства типа Superclass <MyProtocol>. Кроме того, для дополнительной гибкости вы можете использовать префикс ваших методов в протоколе с @optional.
dotToString
Это не похоже на работу в Xcode 5.0.2; он генерирует только предупреждение для «абстрактного» класса. Не расширение «абстрактного» класса генерирует правильное предупреждение компилятора, но, очевидно, не позволяет вам наследовать методы.
Topher Fangio
Я предпочитаю это решение, но оно мне действительно не нравится, оно не очень хорошая структура кода в проекте. // следует использовать это как базовый тип для подклассов. typedef BaseClass <BaseClassProtocol> BASECLASS; Это правило недели, мне это не нравится.
Итачи
35

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

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

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

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

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

- (id)init
{
     [self doesNotRecognizeSelector:_cmd];
     [self release];
     return nil;
}
Питер Мортенсен
источник
1
Я не думал об использовании -doesNotRecognizeSelector: и мне в некотором смысле нравится этот подход. Кто-нибудь знает, как заставить компилятор выдавать предупреждение для «абстрактного» метода, созданного таким образом или с помощью исключения? Это было бы здорово ...
Куинн Тейлор,
26
Подход doNotRecognizeSelector предотвращает использование предложенного Apple шаблона self = [super init].
Длинсин
1
@ Давид: Я уверен, что все дело в том, чтобы как можно скорее выдвинуть исключение. В идеале это должно быть во время компиляции, но, поскольку сделать это невозможно, они установили исключение во время выполнения. Это похоже на ошибку утверждения, которая никогда не должна возникать в производственном коде. Фактически, assert (false) может быть лучше, так как более понятно, что этот код никогда не должен выполняться. Пользователь не может ничего сделать, чтобы исправить это, разработчик должен это исправить. Таким образом, поднятие исключения или ошибки утверждения здесь звучит как хорошая идея.
Чувствительный
2
Я не думаю, что это жизнеспособное решение, поскольку у подклассов могут быть совершенно законные вызовы [super init].
Раффи Хачадурян
@dlinsin: Это именно то, что вы хотите, когда абстрактный класс не должен быть инициализирован через init. Его подклассы, вероятно, знают, какой супер-метод вызывать, но это останавливает небрежный вызов через «new» или «alloc / init».
gnasher729 25.11.16
21

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

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

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

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

Бен Готоу
источник
Мне кажется, что это правильный путь для случаев, когда вы используете абстрактный класс, по крайней мере, когда он действительно означает «интерфейс» (как в C ++). Есть ли скрытые недостатки этого подхода?
Felling
1
@febeling, абстрактные классы - по крайней мере, в Java - это не просто интерфейсы. Они также определяют некоторое (или большинство) поведение. Этот подход может быть хорошим в некоторых случаях.
Дэн Розенстарк
Мне нужен базовый класс для реализации определенных функций, которые все мои подклассы разделяют (устраняя дублирование), но мне также нужен протокол для других методов, которые базовый класс не должен обрабатывать (абстрактная часть). Поэтому мне нужно использовать и то, и другое, поэтому сложно убедиться, что ваши подклассы реализуют себя правильно.
LightningStryk
19

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

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

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

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

  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. Его нельзя удалить в сборках выпуска без генерации других предупреждений компилятора или ошибок - в отличие от подхода, основанного на одном из макросов утверждений.

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

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

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

danyowdee
источник
Это мое новое любимое решение - __builtin_unreachable();драгоценный камень делает эту работу отлично. Макрос делает самодокументирование, и поведение соответствует тому, что происходит, если вы вызываете отсутствующий метод объекта.
Алекс MDC
12

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

Кэмерон Спикерт
источник
7

В 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");

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

Ричард Стеллинг
источник
1
Я не мог получить это. Я применил свое предложение на моем базовый класс -init метод и теперь Xcode не позволяет мне создать экземпляр класса унаследованного и компиляция ошибка времени происходит недоступна ... . Не могли бы вы объяснить больше?
аноним
Убедитесь, что у вас есть -initметод в вашем подклассе.
Ричард Стеллинг
2
Это то же самое, что NS_UNAVAILABLE, который будет вызывать ошибку каждый раз, когда вы попытаетесь вызвать метод, помеченный таким атрибутом. Я не понимаю, как это можно использовать на абстрактном классе.
Бен Синклер
7

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

Вариант 1: протоколы

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

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

Вариант 2: Шаблон Метод Шаблон

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

hadaytullah
источник
6

Еще одна альтернатива

Просто проверьте класс в классе Abstract и Assert или Exception, как вам нравится.

@implementation Orange
- (instancetype)init
{
    self = [super init];
    NSAssert([self class] != [Orange class], @"This is an abstract class");
    if (self) {
    }
    return self;
}
@end

Это устраняет необходимость переопределения init

bigkm
источник
Будет ли эффективно просто вернуть ноль? Или это вызвало бы исключение / другую ошибку?
user2277872
Что ж, это просто для того, чтобы поймать программистов на непосредственное использование класса, что, я думаю, поможет использовать assert.
bigkm
5

(больше связанного предложения)

Я хотел иметь способ дать программисту знать «не звонить от ребенка» и полностью переопределить (в моем случае все еще предлагать некоторую функциональность по умолчанию от имени родителя, когда он не расширен):

typedef void override_void;
typedef id override_id;

@implementation myBaseClass

// some limited default behavior (undesired by subclasses)
- (override_void) doSomething;
- (override_id) makeSomeObject;

// some internally required default behavior
- (void) doesSomethingImportant;

@end

Преимущество состоит в том, что программист увидит «переопределение» в объявлении и будет знать, что он не должен вызывать [super ..].

Конечно, для этого некрасиво определять отдельные типы возвращаемых данных, но это служит достаточно хорошим визуальным советом, и вы не можете легко использовать часть «override_» в определении подкласса.

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

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

пример подсказки

Иван Досев
источник
4

Если вы привыкли к тому, что компилятор отлавливает нарушения абстрактных реализаций в других языках, то поведение Objective-C разочаровывает.

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

Вот образец, который мы используем, чтобы получить этот тип статической проверки, используя несколько методов, чтобы скрыть инициализаторы:

//
//  Base.h
#define UNAVAILABLE __attribute__((unavailable("Default initializer not available.")));

@protocol MyProtocol <NSObject>
-(void) dependentFunction;
@end

@interface Base : NSObject {
    @protected
    __weak id<MyProtocol> _protocolHelper; // Weak to prevent retain cycles!
}

- (instancetype) init UNAVAILABLE; // Prevent the user from calling this
- (void) doStuffUsingDependentFunction;
@end

//
//  Base.m
#import "Base.h"

// We know that Base has a hidden initializer method.
// Declare it here for readability.
@interface Base (Private)
- (instancetype)initFromDerived;
@end

@implementation Base
- (instancetype)initFromDerived {
    // It is unlikely that this becomes incorrect, but assert
    // just in case.
    NSAssert(![self isMemberOfClass:[Base class]],
             @"To be called only from derived classes!");
    self = [super init];
    return self;
}

- (void) doStuffUsingDependentFunction {
    [_protocolHelper dependentFunction]; // Use it
}
@end

//
//  Derived.h
#import "Base.h"

@interface Derived : Base
-(instancetype) initDerived; // We cannot use init here :(
@end

//
//  Derived.m
#import "Derived.h"

// We know that Base has a hidden initializer method.
// Declare it here.
@interface Base (Private)
- (instancetype) initFromDerived;
@end

// Privately inherit protocol
@interface Derived () <MyProtocol>
@end

@implementation Derived
-(instancetype) initDerived {
    self= [super initFromDerived];
    if (self) {
        self->_protocolHelper= self;
    }
    return self;
}

// Implement the missing function
-(void)dependentFunction {
}
@end
Пересекать_
источник
3

Вероятно, такого рода ситуации должны происходить только во время разработки, поэтому это может сработать:

- (id)myMethodWithVar:(id)var {
   NSAssert(NO, @"You most override myMethodWithVar:");
   return nil;
}
Juanjo
источник
3

Вы можете использовать метод, предложенный @Yar (с некоторыми изменениями):

#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]
#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()

Здесь вы получите сообщение типа:

<Date> ProjectName[7921:1967092] <Class where method not implemented> - method not implemented
<Date> ProjectName[7921:1967092] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[<Base class (if inherited or same if not> <Method name>] must be overridden in a subclass/category'

Или утверждение:

NSAssert(![self respondsToSelector:@selector(<MethodName>)], @"Not implemented");

В этом случае вы получите:

<Date> ProjectName[7926:1967491] *** Assertion failure in -[<Class Name> <Method name>], /Users/kirill/Documents/Projects/root/<ProjectName> Services/Classes/ViewControllers/YourClass:53

Также вы можете использовать протоколы и другие решения - но это одно из самых простых.

GBK
источник
2

Какао не дает ничего, что называется абстрактным. Мы можем создать реферат класса, который проверяется только во время выполнения, а во время компиляции это не проверяется.

Хуссейн Шаббир
источник
1

Я обычно просто отключаю метод init в классе, который хочу абстрагировать:

- (instancetype)__unavailable init; // This is an abstract class.

Это сгенерирует ошибку во время компиляции всякий раз, когда вы вызываете init для этого класса. Затем я использую методы класса для всего остального.

Objective-C не имеет встроенного способа объявления абстрактных классов.

Али
источник
Но я получаю ошибку, когда я вызываю [super init] из производного класса этого абстрактного класса. Как это решить?
Сахил Доши
@ SahilDoshi Я полагаю, что это недостаток использования этого метода. Я использую его, когда не хочу создавать экземпляр класса, и ничто не наследуется от этого класса.
Али
да, я понял это но ваше решение поможет создать нечто вроде синглтон-класса, где мы не хотим, чтобы кто-либо вызывал init. Насколько мне известно, в случае абстрактного класса некоторый класс будет наследовать его. еще что за использование абстрактного класса.
Сахил Доши
0

Немного изменив то, что предложил @redfood, применив комментарий @ dotToString, вы фактически получаете решение, принятое IGListKit из Instagram .

  1. Создайте протокол для всех методов, которые не имеют смысла определяться в базовом (абстрактном) классе, т. Е. Им нужны конкретные реализации в дочерних элементах.
  2. Создать базовый (абстрактный) класс, который не реализует этот протокол. Вы можете добавить в этот класс любые другие методы, которые имеют смысл иметь общую реализацию.
  3. Везде в вашем проекте, если ребенок из AbstractClass должен вводиться или выводиться каким-либо методом, введите его как AbstractClass<Protocol>взамен.

Поскольку AbstractClassне реализует Protocol, единственный способ получить AbstractClass<Protocol>экземпляр - это создать подклассы. Поскольку AbstractClassодно только не может использоваться нигде в проекте, оно становится абстрактным.

Конечно, это не мешает сторонним разработчикам добавлять новые методы, ссылающиеся просто на них AbstractClass, что в конечном итоге позволило бы создать экземпляр (больше не) абстрактного класса.

Пример из реальной жизни : IGListKit имеет базовый класс, IGListSectionControllerкоторый не реализует протокол IGListSectionType, однако каждый метод, которому требуется экземпляр этого класса, фактически запрашивает тип IGListSectionController<IGListSectionType>. Поэтому нет никакого способа использовать объект типа IGListSectionControllerдля чего-либо полезного в их структуре.

Gobe
источник
0

На самом деле Objective-C не имеет абстрактных классов, но вы можете использовать протоколы для достижения того же эффекта. Вот образец:

CustomProtocol.h

#import <Foundation/Foundation.h>

@protocol CustomProtocol <NSObject>
@required
- (void)methodA;
@optional
- (void)methodB;
@end

TestProtocol.h

#import <Foundation/Foundation.h>
#import "CustomProtocol.h"

@interface TestProtocol : NSObject <CustomProtocol>

@end

TestProtocol.m

#import "TestProtocol.h"

@implementation TestProtocol

- (void)methodA
{
  NSLog(@"methodA...");
}

- (void)methodB
{
  NSLog(@"methodB...");
}
@end
YorkFish
источник
0

Простой пример создания абстрактного класса

// Declare a protocol
@protocol AbcProtocol <NSObject>

-(void)fnOne;
-(void)fnTwo;

@optional

-(void)fnThree;

@end

// Abstract class
@interface AbstractAbc : NSObject<AbcProtocol>

@end

@implementation AbstractAbc

-(id)init{
    self = [super init];
    if (self) {
    }
    return self;
}

-(void)fnOne{
// Code
}

-(void)fnTwo{
// Code
}

@end

// Implementation class
@interface ImpAbc : AbstractAbc

@end

@implementation ImpAbc

-(id)init{
    self = [super init];
    if (self) {
    }
    return self;
}

// You may override it    
-(void)fnOne{
// Code
}
// You may override it
-(void)fnTwo{
// Code
}

-(void)fnThree{
// Code
}

@end
Say2Manuj
источник
-3

Вы не можете просто создать делегата?

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

Затем всякий раз, когда вы реализуете свой делегат (т.е. абстрактный класс), компилятор предупреждает вас о том, для каких необязательных и обязательных функций вам нужно определить поведение.

Это звучит как абстрактный базовый класс для меня.

user1709076
источник