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

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

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

Ответ 1

Обычно класс 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];

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

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

Ответ 2

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

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

Например:

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

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

Ответ 3

Просто риффы на ответ @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
}



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

Ответ 4

Решение, с которым я столкнулся, это:

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

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

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

Ответ 5

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

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

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

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

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

- (id)init
{
     [self doesNotRecognizeSelector:_cmd];
     [self release];
     return nil;
}

Ответ 6

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

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

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

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

Ответ 7

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

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

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

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

  • Реализовать каждый абстрактный метод в базовом классе.
  • Сделайте эту реализацию [self doesNotRecognizeSelector:_cmd];...
  • ... за которым следует __builtin_unreachable();, чтобы отключить предупреждение, которое вы получите для непустых методов, сообщив вам, что "контроль достигнут конца не-void-функции без возврата".
  • Комбинируйте шаги 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.)

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

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

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

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

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

Ответ 8

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

Ответ 9

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

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

Ответ 10

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

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

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

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

Вариант 2: шаблон шаблона шаблона

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

Ответ 11

Другая альтернатива

Просто проверьте класс в классе 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

Ответ 12

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

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

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_" в определении подкласса.

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

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

example of the hint

Ответ 13

Если вы привыкли к компилятору, улавливающему абстрактные нарушения экземпляров на других языках, поведение 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

Ответ 14

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

- (id)myMethodWithVar:(id)var {
   NSAssert(NO, @"You most override myMethodWithVar:");
   return nil;
}

Ответ 15

Вы можете использовать метод, предложенный @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

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

Ответ 16

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

Ответ 17

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

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

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

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

Ответ 18

Фактически, 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

Ответ 19

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

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

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

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

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

Ответ 20

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

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

Ответ 21

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

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

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

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