Створення абстрактного класу в 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?Ab абстрактSuperClass

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це працює дуже добре.
Патрік Пійнаппель

Це вийшло для мене, оскільки, ІМХО, викидання виключення є більш очевидним для інших розробників, що це бажана поведінка doesNotRecognizeSelector.
Кріс

Використовуйте протоколи (для повністю абстрактного класу) або шаблон методу шаблону, де абстрактний клас має часткову логіку реалізації / потоку, як тут наведено stackoverflow.com/questions/8146439/… . Дивіться мою відповідь нижче.
hadaytullah

267

Ні, немає ніякого способу створити абстрактний клас в Objective-C.

Ви можете знущатися над абстрактним класом - зробивши виклики методів / селекторів doesNotRecognizeSelector: і тому підняти виняток, що робить клас непридатним.

Наприклад:

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

Ви також можете зробити це для init.


5
@Chuck, я не спровокував, але NSObjectпосилання пропонує використовувати це там, де ви НЕ хочете успадковувати метод, а не примушувати переосмислення методу. Хоча це може бути те саме, можливо :)
Dan Rosenstark

Чому б ви не хотіли просто використовувати протокол у цьому випадку? Мені це приємно знати лише для заглушок методів, але не для цілого абстрактного класу. Вигляд використання для цього видається обмеженим.
lewiguez

Я не спровокував це, але припущення про те, що ви повинні викликати виняток, викликане методом init, є найбільш ймовірною причиною цього. Найпоширеніший формат підкласу запустить його власний метод init, викликавши self = [super init] - який слухняно кине виняток. Цей формат добре працює для більшості методів, але я б ніколи не робив цього в тому, де підклас міг би називати його суперреалізацією.
Xono

5
Ви абсолютно можете створювати абстрактні класи в Objective-C, і це досить часто. Існує ряд рамкових класів Apple, які роблять це. Абстрактні класи Apple викидають конкретні винятки (NSInvalidArgumentException), часто викликаючи NSInvalidAb абстрактInvocation (). Виклик абстрактного методу - помилка програмування, тому він кидає виняток. Абстрактні фабрики зазвичай реалізуються як кластери класів.
чудовий

2
@quellish: Як ви вже говорили: виклик абстрактного методу - помилка програмування. Це слід розглядати як таке, а не покладатися на повідомлення про помилки під час виконання (NSException). Це, на мою думку, є найбільшою проблемою для розробників, які надходять з інших мов, де абстрактне означає "не може створити екземпляр такого типу" в Obj-C, це означає, що "все піде не так під час виконання, коли ви інстанціюєте цей клас".
Хрест_

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. Велике задоволення, заощадження тисяч букв коду ...
Dan Rosenstark

Чудово. Змінена це небагато, хоча: #define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil].
noamtm

1
@Yar: Я не думаю, що так. Ми використовуємо __PRETTY_FUNCTION__всюди через макрос DLog (...), як тут пропонується: stackoverflow.com/a/969291/38557 .
noamtm

1
для більш детальної інформації -#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()
gbk

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, але ви отримуєте потрібне попередження компілятора.


2
+1 Це дійсно рішення, яке найближче до абстрактного класу на Java. Я сам використовував такий підхід, і він чудово працює. Дозволено навіть називати протокол таким же, як базовий клас (так, як це робив Apple NSObject). Якщо потім помістити протокол і декларацію базового класу в один і той же файл заголовка, він майже не відрізняється від абстрактного класу.
codingFriend1

Ах, але мій абстрактний клас реалізує частину протоколу, решта реалізується підкласами.
Roger CS Wernersson

1
ви могли б лише дочірні класи реалізувати протокол і залишити методи суперкласу всі разом замість порожнього. то мають властивості типу Superclass <MyProtocol>. Крім того, для додаткової гнучкості ви можете префіксувати свої методи у своєму протоколі за допомогою @optional.
dotToString

Схоже, це не працює в Xcode 5.0.2; він генерує лише попередження для "абстрактного" класу. Нерозширення класу "абстрактних" породжує належне попередження компілятора, але, очевидно, не дає вам успадкувати методи.
Topher Fangio

Я вважаю за краще це рішення, але йому це дуже не подобається, воно насправді не є гарною структурою коду в проекті. // повинен використовувати це як базовий тип для підкласів. typedef BaseClass <BaseClassProtocol> BASECLASS; Це правило всього тижня, мені це не подобається.
Ітачі

35

Із списку розсилки групи Omni :

На даний момент у 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].
dlinsin

1
@david: Я впевнений, що вся справа в тому, щоб якомога швидше створити виняток. В ідеалі це повинно бути під час компіляції, але оскільки немає можливості це зробити, вони вирішили виняток із часу виконання. Це схоже на недолік твердження, який ніколи не слід піднімати у виробничому коді. Насправді, твердження (false) насправді може бути кращим, оскільки ясніше, що цей код ніколи не повинен працювати. Користувач не може нічого зробити, щоб виправити це, розробник повинен це виправити. Таким чином, підняття винятку або відмови у твердженні тут звучить як добра ідея.
Розсудливий

2
Я не думаю, що це є прийнятним рішенням, оскільки підкласи можуть мати цілком законні дзвінки до [super init].
Раффі Хатчадуріан

@dlinsin: Саме цього ви хочете, коли абстрактний клас не повинен бути ініціалізований через init. Її підкласи, мабуть, знають, який супер метод викликати, але це зупиняє недбале виклик через "new" або "alloc / init".
gnasher729

21

Замість того, щоб намагатися створити абстрактний базовий клас, подумайте про використання протоколу (подібного до інтерфейсу Java). Це дозволяє визначити набір методів, а потім прийняти всі об'єкти, які відповідають протоколу, та реалізувати методи. Наприклад, я можу визначити протокол Operation, а потім мати таку функцію:

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

Де op може бути будь-який об'єкт, що реалізує протокол Operation.

Якщо вам потрібен ваш абстрактний базовий клас, щоб не просто визначити методи, ви можете створити звичайний клас Objective-C і запобігти його інстанцізації. Просто замініть функцію - (id) init і змусьте її повернути нуль або затвердити (помилково). Це не дуже чисте рішення, але оскільки Objective-C є повністю динамічним, насправді немає прямого еквівалента абстрактному базовому класу.


Мені здається, це підходящий шлях для випадків, коли ви б використовували абстрактний клас, принаймні, коли це означає «інтерфейс» дійсно (як, наприклад, у C ++). Чи є якісь приховані мінуси цього підходу?
підступ

1
@febeling, абстрактні класи - принаймні на Java - не просто інтерфейси. Вони також визначають деяку (або більшість) поведінку. Цей підхід може бути корисним у деяких випадках.
Дан Розенстарк

Мені потрібен базовий клас для реалізації певної функціональності, якою поділяються всі мої підкласи (видалення дублювання), але мені також потрібен протокол для інших методів, з якими базовий клас не повинен обробляти (абстрактна частина). Тож мені потрібно використовувати і те, і інше, коли це може стати складним, щоб переконатися, що ваші підкласи реалізують себе належним чином.
Блискавка

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__))категорію без реалізації, щоб не замінити оригінальну реалізацію цього методу, і включіть заголовок цієї категорії до ПКН вашого проекту.

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

Ось:

// 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

Як бачите, макрос забезпечує повну реалізацію абстрактних методів, зводячи необхідну кількість котлоагрегату до абсолютного мінімуму.

Ще кращим варіантом було б лобіювати команду Кланг у наданні атрибута компілятора для цього випадку за допомогою запитів на функції. (Краще, тому що це також дозволить діагностувати час компіляції для тих сценаріїв, де ви підклас, наприклад, NSIncrementalStore.)

Чому я вибираю цей метод

  1. Робота виконується ефективно та дещо зручно.
  2. Це досить легко зрозуміти. (Гаразд, це __builtin_unreachable()може здивувати людей, але це теж досить легко зрозуміти.)
  3. Вона не може бути позбавлена ​​версій версій без генерування інших попереджень компілятора чи помилок - на відміну від підходу, заснованого на одному з макросів твердження.

Останній пункт потребує певного пояснення:

Деякі (більшість?) Людей знімають твердження у версії версій. (Я не погоджуюся з цією звичкою, але це вже інша історія…) Якщо не реалізувати потрібний метод - однак - це погано , страшно , неправильно і, в основному, кінець Всесвіту для вашої програми. Ваша програма не може правильно працювати в цьому плані, оскільки вона не визначена, а невизначена поведінка - це найгірше, що коли-небудь було. Отже, можливість зняти діагностику без створення нової діагностики було б абсолютно неприйнятним.

Це досить погано, що ви не можете отримати належну діагностику часу компіляції для таких помилок програміста, і вам доведеться вдатися до виявлення під час запуску для них, але якщо ви можете зафіксувати це у версії версій, чому спробуйте мати абстрактний клас у першість?


Це моє нове улюблене рішення - __builtin_unreachable();дорогоцінний камінь робить цю роботу ідеальною. Макрос робить його самодокументованим і поведінка відповідає тому, що станеться, якщо ви викликаєте відсутній метод на об'єкті.
Алекс МДЦ

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: протоколи

Якщо ви хочете створити абстрактний клас без реалізації, використовуйте 'Protocols'. Класи, що успадковують протокол, зобов'язані реалізувати методи в протоколі.

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

Варіант2: Шаблон методу шаблону

Якщо ви хочете створити абстрактний клас з частковою реалізацією на зразок "Шаблон методу шаблону", то це рішення. Objective-C - Шаблон методів шаблону?


6

Ще одна альтернатива

Просто перевірте клас у класі "Анотація" та "Затвердити" або "Виняток", що б вам не здалося.

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

Це усуває необхідність перекриття init


Чи було б ефективно просто повернути нуль? Або це може спричинити викид виключення / іншої помилки?
користувач2277872

Ну це просто для того, щоб зловити програмістів для використання класу безпосередньо, що, на мою думку, добре використовує аргумент.
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;
}

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

Також ви можете використовувати протоколи та інші рішення - але це одне з найпростіших.


2

Какао не дає нічого, що називається абстрактним. Ми можемо створити конспект класу, який перевіряється лише під час виконання, а під час компіляції це не перевіряється.


1

Зазвичай я просто відключаю метод init у класі, який я хочу абстрагувати:

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

Це призведе до помилки під час компіляції кожного разу, коли ви викликаєте init у цьому класі. Тоді я використовую методи класу для всього іншого.

У Objective-C немає вбудованого способу декларування абстрактних класів.


Але я отримую помилку, коли дзвоню [super init] із похідного класу цього абстрактного класу. Як це вирішити?
Сахіл Доші

@SahilDoshi Я вважаю, що це є недоліком використання цього методу. Я використовую його, коли я не хочу дозволити екземпляру класу, і нічого не успадковується від цього класу.
mahoukov

так, я це зрозумів. але ваше рішення допоможе створити щось на кшталт одиночного класу, де ми не хочемо, щоб хтось викликав init. за моїми знаннями у випадку абстрактного класу, якийсь клас буде його успадковувати. ще використання абстрактного класу.
Сахіл Доші

0

Змінивши трохи те, що запропонував @redfood, застосувавши коментар @ dotToString, ви насправді маєте рішення, прийняте IGListKit Instagram .

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

Оскільки AbstractClassне реалізується Protocol, єдиний спосіб мати AbstractClass<Protocol>екземпляр - це підкласифікація. Оскільки AbstractClassпоодинці не можна використовувати ніде в проекті, він стає абстрактним.

Звичайно, це не заважає розробникам без AbstractClassдозволу додавати нові методи, що посилаються просто на , що в результаті дозволить отримати екземпляр (вже не) абстрактного класу.

Приклад реального світу: IGListKit має базовий клас, IGListSectionControllerякий не реалізує протокол IGListSectionType, проте кожен метод, який вимагає екземпляр цього класу, насправді запитує тип IGListSectionController<IGListSectionType>. Тому немає можливості використовувати об’єкт типу IGListSectionControllerдля нічого корисного в їх рамках.


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

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

-3

Ви не можете просто створити делегата?

Делегат - це як абстрактний базовий клас у тому сенсі, що ви говорите, які функції потрібно визначити, але ви насправді не визначаєте їх.

Тоді, коли ви реалізуєте свого делегата (тобто абстрактного класу), компілятор попереджає про те, які необов’язкові та обов'язкові функції вам потрібно визначити для поведінки.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.