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

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

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

вопрос задан 23.06.2009
Jonathan Arbogast
6690 репутация

21 ответов


  • 625 рейтинг

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

    ответ дан Barry Wark, с репутацией 99877, 23.06.2009
  • 267 рейтинг

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

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

    Например:

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

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

    ответ дан Grouchal, с репутацией 8567, 23.06.2009
  • 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 документы).

    ответ дан Dan Rosenstark, с репутацией 41026, 23.06.2011
  • 41 рейтинг

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

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

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

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

    ответ дан redfood, с репутацией 599, 21.06.2012
  • 34 рейтинг

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

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

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

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

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

    - (id)init
    {
         [self doesNotRecognizeSelector:_cmd];
         [self release];
         return nil;
    }
    
    ответ дан lolsborn, с репутацией 933, 23.06.2009
  • 21 рейтинг

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

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

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

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

    ответ дан Ben Gotow, с репутацией 12682, 23.06.2009
  • 18 рейтинг

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

    Однако, мой любимый метод не упомянут, и у 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 Team для предоставления атрибута компилятора для этого случая через запросы функций. (Лучше, потому что это также позволило бы диагностику во время компиляции для тех сценариев, где вы подкласс e. г. NSIncrementalStore. )

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

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

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

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

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

    ответ дан danyowdee, с репутацией 4098, 23.07.2013
  • 12 рейтинг

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

    ответ дан Cameron Spickert, с репутацией 4559, 3.11.2010
  • 7 рейтинг

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

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

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

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

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

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

    ответ дан hadaytullah, с репутацией 945, 27.03.2014
  • 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");
    

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

    ответ дан rjstelling, с репутацией 17419, 6.11.2012
  • 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, с репутацией 1829, 8.12.2013
  • 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_ в определении подкласса.

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

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

    example of the hint

    ответ дан Ivan Dossev, с репутацией 500, 3.05.2012
  • 4 рейтинг

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

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

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

    //
    //  Base.h
    #define UNAVAILABLE __attribute__((unavailable("Default initializer not available.")));
    
    @protocol MyProtocol 
    -(void) dependentFunction;
    @end
    
    @interface Base : NSObject {
        @protected
        __weak id _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 () 
    @end
    
    @implementation Derived
    -(instancetype) initDerived {
        self= [super initFromDerived];
        if (self) {
            self->_protocolHelper= self;
        }
        return self;
    }
    
    // Implement the missing function
    -(void)dependentFunction {
    }
    @end
    
    ответ дан Cross_, с репутацией 316, 7.07.2015
  • 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()
    

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

     ProjectName[7921:1967092]  - method not implemented
     ProjectName[7921:1967092] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[ ] must be overridden in a subclass/category'
    

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

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

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

     ProjectName[7926:1967491] *** Assertion failure in -[ ], /Users/kirill/Documents/Projects/root/ Services/Classes/ViewControllers/YourClass:53
    

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

    ответ дан gbk, с репутацией 6405, 2.08.2015
  • 3 рейтинг

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

    - (id)myMethodWithVar:(id)var {
       NSAssert(NO, @"You most override myMethodWithVar:");
       return nil;
    }
    
    ответ дан juanjo, с репутацией 2609, 26.04.2013
  • 2 рейтинг

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

    ответ дан Hussain Shabbir, с репутацией 11167, 29.10.2014
  • 1 рейтинг

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

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

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

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

    ответ дан Mahouk, с репутацией 730, 20.08.2015
  • 0 рейтинг

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

    // Declare a protocol
    @protocol AbcProtocol 
    
    -(void)fnOne;
    -(void)fnTwo;
    
    @optional
    
    -(void)fnThree;
    
    @end
    
    // Abstract class
    @interface AbstractAbc : NSObject
    
    @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, с репутацией 420, 25.03.2017
  • 0 рейтинг

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

    CustomProtocol. ч

    #import 
    
    @protocol CustomProtocol 
    @required
    - (void)methodA;
    @optional
    - (void)methodB;
    @end
    

    TestProtocol. ч

    #import 
    #import "CustomProtocol.h"
    
    @interface TestProtocol : NSObject 
    
    @end
    

    TestProtocol. м

    #import "TestProtocol.h"
    
    @implementation TestProtocol
    
    - (void)methodA
    {
      NSLog(@"methodA...");
    }
    
    - (void)methodB
    {
      NSLog(@"methodB...");
    }
    @end
    
    ответ дан york, с репутацией 37, 2.04.2014
  • 0 рейтинг

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

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

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

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

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

    ответ дан Gobe, с репутацией 858, 18.10.2016
  • -3 рейтинг

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

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

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

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

    ответ дан user1709076, с репутацией 880, 22.05.2014