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

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

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

Ответы [ 21 ]

6 голосов
/ 08 декабря 2013

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

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

5 голосов
/ 03 мая 2012

(больше похожего предложения)

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

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

4 голосов
/ 07 июля 2015

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

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

#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

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

3 голосов
/ 26 апреля 2013

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

- (id)myMethodWithVar:(id)var {
   NSAssert(NO, @"You most override myMethodWithVar:");
   return nil;
}
2 голосов
/ 29 октября 2014

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

1 голос
/ 20 августа 2015

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

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

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

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

0 голосов
/ 25 марта 2017

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

// 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
0 голосов
/ 18 октября 2016

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

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

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

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

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

0 голосов
/ 22 мая 2014

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

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

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

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

...