Crear una clase abstracta en Objective-C

507

Originalmente soy un programador de Java que ahora trabaja con Objective-C. Me gustaría crear una clase abstracta, pero eso no parece ser posible en Objective-C. es posible?

Si no, ¿qué tan cerca de una clase abstracta puedo obtener en Objective-C?

Jonathan Arbogast
fuente
18
Las respuestas a continuación son geniales. Creo que el problema de las clases abstractas está tangencialmente relacionado con los métodos privados: ambos son métodos para restringir lo que el código del cliente puede hacer, y ninguno de los dos existe en Objective-C. Creo que ayuda a entender que la mentalidad del lenguaje en sí es fundamentalmente diferente de Java. Vea mi respuesta a: stackoverflow.com/questions/1020070/#1020330
Quinn Taylor
Gracias por la información sobre la mentalidad de la comunidad Objective-C en comparación con otros idiomas. Eso realmente resuelve una serie de preguntas relacionadas que tuve (como por qué no hay un mecanismo directo para métodos privados, etc.).
Jonathan Arbogast
1
así que eche un vistazo al sitio CocoaDev que le da una comparación de java cocoadev.com/index.pl?AbstractSuperClass
2
Aunque Barry lo menciona como un pensamiento posterior (perdóneme si lo estoy leyendo mal), creo que está buscando un Protocolo en el Objetivo C. Vea, por ejemplo, ¿Qué es un Protocolo? .
jww

Respuestas:

633

Por lo general, las clases Objective-C son abstractas solo por convención; si el autor documenta una clase como abstracta, simplemente no la use sin subclasificarla. Sin embargo, no existe una aplicación en tiempo de compilación que evite la creación de instancias de una clase abstracta. De hecho, no hay nada que impida a un usuario proporcionar implementaciones de métodos abstractos a través de una categoría (es decir, en tiempo de ejecución). Puede obligar a un usuario a al menos anular ciertos métodos al generar una excepción en la implementación de esos métodos en su clase abstracta:

[NSException raise:NSInternalInconsistencyException 
            format:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)];

Si su método devuelve un valor, es un poco más fácil de usar

@throw [NSException exceptionWithName:NSInternalInconsistencyException
                               reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]
                             userInfo:nil];

ya que no necesita agregar una declaración de devolución del método.

Si la clase abstracta es realmente una interfaz (es decir, no tiene implementaciones de métodos concretos), usar un protocolo Objective-C es la opción más apropiada.

Barry Wark
fuente
18
Creo que la parte más apropiada de la respuesta fue mencionar que podría usar @protocol en lugar de solo definir métodos.
Chad Stewart
13
Para aclarar: puede declarar métodos en una @protocoldefinición, pero no puede definir los métodos allí.
Richard
Con un método de categoría en NSException + (instancetype)exceptionForCallingAbstractMethod:(SEL)selectoresto funciona muy bien.
Patrick Pijnappel
Esto funcionó para mí ya que, en mi humilde opinión, lanzar una excepción es más obvio para otros desarrolladores de que este es un comportamiento deseado más que doesNotRecognizeSelector.
Chris
Utilice protocolos (para una clase completamente abstracta) o un patrón de método de plantilla donde la clase abstracta tenga una implementación parcial / lógica de flujo como se indica aquí stackoverflow.com/questions/8146439/… . Vea mi respuesta a continuación.
hadaytullah
267

No, no hay forma de crear una clase abstracta en Objective-C.

Puede burlarse de una clase abstracta, haciendo que los métodos / selectores llamen doesNotRecognizeSelector: y, por lo tanto, genere una excepción que inutilice la clase.

Por ejemplo:

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

También puedes hacer esto para init.

Grouchal
fuente
55
@Chuck, no voté en contra, pero la NSObjectreferencia sugiere usar esto donde NO quieres heredar un método, no para forzar la anulación de un método. Aunque pueden ser lo mismo, quizás :)
Dan Rosenstark
¿Por qué no querrías usar un protocolo en este caso? Para mí, es bueno saberlo solo para los stubs de método, pero no para toda una clase abstracta. El caso de uso para esto parece limitado.
lewiguez
No lo rechacé, pero la sugerencia de que debería provocar una excepción en el método init es la razón más probable. El formato más común para una subclase comenzará su propio método init llamando a self = [super init], lo que arrojará obedientemente una excepción. Este formato funciona bien para la mayoría de los métodos, pero nunca lo haría en algo donde la subclase podría llamar su súper implementación.
Xono
55
Absolutamente puede crear clases abstractas en Objective-C, y es bastante común. Hay varias clases de framework de Apple que hacen esto. Las clases abstractas de Apple lanzan excepciones específicas (NSInvalidArgumentException), a menudo llamando a NSInvalidAbstractInvocation (). Llamar a un método abstracto es un error de programación, por lo que arroja una excepción. Las fábricas abstractas se implementan comúnmente como grupos de clases.
quellish
2
@quellish: Como dijiste: llamar a un método abstracto es un error de programación. Debe tratarse como tal, en lugar de depender de los informes de errores en tiempo de ejecución (NSException). Creo que este es el mayor problema para los desarrolladores que vienen de otros idiomas, donde abstracto significa "no puede crear una instancia de un objeto de este tipo" en Obj-C significa "las cosas saldrán mal en tiempo de ejecución cuando crea una instancia de esta clase".
Cross_
60

Simplemente analizando la respuesta anterior de @Barry Wark (y actualizando para iOS 4.3) y dejando esto para mi propia referencia:

#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]
#define methodNotImplemented() mustOverride()

entonces en tus métodos puedes usar esto

- (void) someMethod {
     mustOverride(); // or methodNotImplemented(), same thing
}



Notas: No estoy seguro de si hacer que una macro se vea como una función C es una buena idea o no, pero la mantendré hasta que se demuestre lo contrario. Creo que es más correcto usar NSInvalidArgumentException(en lugar de NSInternalInconsistencyException) ya que eso es lo que arroja el sistema de tiempo de ejecución en respuesta a doesNotRecognizeSelectorser llamado (ver NSObjectdocumentos).

Dan Rosenstark
fuente
1
Claro, @TomA, espero que te dé ideas para otro código que puedas macro. Mi macro más utilizada es una referencia simple a un singleton: el código dice universe.thingpero se expande a [Universe universe].thing. Gran diversión, ahorrando miles de letras de código ...
Dan Rosenstark
Excelente. Cambiado un poco, sin embargo: #define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil].
noamtm
1
@Yar: No lo creo. Usamos __PRETTY_FUNCTION__todo el lugar, a través de una macro DLog (...), como se sugiere aquí: stackoverflow.com/a/969291/38557 .
noamtm
1
para más detalles -#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()
gbk
1
si agrega la clase base y luego hereda esta clase, por ejemplo, 10 veces y olvidó implementar esto en una de las clases, recibirá un mensaje con el nombre de la clase Base no heredada, como Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[BaseDynamicUIViewController localizeUI] must be overridden in a subclass/category'en el caso de que proponga que también obtenga HomeViewController - method not implementeddonde HomeViewController heredó de Base - esto dará más información
gbk
42

La solución que se me ocurrió es:

  1. Crea un protocolo para todo lo que quieras en tu clase "abstracta"
  2. Cree una clase base (o tal vez llámela abstracta) que implemente el protocolo. Para todos los métodos que desea "abstractos", impleméntelos en el archivo .m, pero no en el archivo .h.
  3. Haga que su clase secundaria herede de la clase base e implemente el protocolo.

De esta manera, el compilador le dará una advertencia sobre cualquier método en el protocolo que no esté implementado por su clase secundaria.

No es tan breve como en Java, pero obtienes la advertencia del compilador deseada.

comida roja
fuente
2
+1 Esta es realmente la solución que más se acerca a una clase abstracta en Java. He utilizado este enfoque yo mismo y funciona muy bien. Incluso se permite nombrar el protocolo igual que la clase base (tal como lo hizo Apple con NSObject). Si luego coloca el protocolo y la declaración de la clase base en el mismo archivo de encabezado, es casi indistinguible de una clase abstracta.
codingFriend1
Ah, pero mi clase abstracta implementa parte de un protocolo, el resto se implementa mediante subclases.
Roger CS Wernersson
1
solo puede hacer que las clases secundarias implementen el protocolo y dejen los métodos de superclase todos juntos en lugar de en blanco. entonces tiene propiedades de tipo Superclase <MyProtocol>. Además, para mayor flexibilidad, puede prefijar sus métodos en su protocolo con @optional.
dotToString
Esto no parece funcionar en Xcode 5.0.2; solo genera una advertencia para la clase "abstracta". No extender la clase "abstracta" genera la advertencia del compilador adecuada, pero obviamente no le permite heredar los métodos.
Topher Fangio
Prefiero esta solución, pero realmente no me gusta, realmente no es una buena estructura de código en el proyecto. // debería usar esto como tipo base para subclases. typedef BaseClass <BaseClassProtocol> BASECLASS; Es solo una regla de una semana, no me gusta.
Itachi
35

De la lista de correo del Grupo Omni :

Objective-C no tiene la construcción del compilador abstracto como Java en este momento.

Entonces, todo lo que debe hacer es definir la clase abstracta como cualquier otra clase normal e implementar stubs de métodos para los métodos abstractos que están vacíos o informan que no son compatibles con el selector. Por ejemplo...

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

También hago lo siguiente para evitar la inicialización de la clase abstracta a través del inicializador predeterminado.

- (id)init
{
     [self doesNotRecognizeSelector:_cmd];
     [self release];
     return nil;
}
Peter Mortensen
fuente
1
No había pensado en usar -doesNotRecognizeSelector: y de alguna manera me gusta este enfoque. ¿Alguien sabe de una manera de hacer que el compilador emita una advertencia para un método "abstracto" creado de esta manera o mediante una excepción? Eso sería increíble ...
Quinn Taylor
26
El enfoque doesNotRecognizeSelector evita el patrón self = [super init] sugerido por Apple.
dlinsin
1
@david: estoy bastante seguro de que el objetivo es plantear una excepción lo antes posible. Idealmente, debería estar en tiempo de compilación, pero como no hay forma de hacerlo, se conformaron con una excepción de tiempo de ejecución. Esto es similar a un fallo de aserción, que nunca debe plantearse en el código de producción en primer lugar. De hecho, una afirmación (falsa) en realidad podría ser mejor ya que es más claro que este código nunca debería ejecutarse. El usuario no puede hacer nada para arreglarlo, el desarrollador debe arreglarlo. Por lo tanto, plantear una excepción o falla de aserción aquí parece una buena idea.
Senseful
2
No creo que esta sea una solución viable ya que las subclases pueden tener llamadas perfectamente legítimas a [super init].
Raffi Khatchadourian
@dlinsin: Eso es exactamente lo que quieres cuando no se supone que la clase abstracta se inicialice a través de init. Es probable que sus subclases sepan qué súper método llamar, pero esto detiene la invocación descuidada a través de "new" o "alloc / init".
gnasher729
21

En lugar de intentar crear una clase base abstracta, considere usar un protocolo (similar a una interfaz Java). Esto le permite definir un conjunto de métodos y luego aceptar todos los objetos que se ajustan al protocolo e implementar los métodos. Por ejemplo, puedo definir un protocolo de operación y luego tener una función como esta:

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

Donde op puede ser cualquier objeto que implemente el protocolo Operation.

Si necesita que su clase base abstracta haga más que simplemente definir métodos, puede crear una clase regular de Objective-C y evitar que se cree una instancia. Simplemente anule la función - (id) init y haga que devuelva nil o afirmar (false). No es una solución muy limpia, pero dado que Objective-C es completamente dinámico, en realidad no hay un equivalente directo a una clase base abstracta.

Ben Gotow
fuente
Para mí, esta parece ser la forma apropiada de ir en los casos en los que usaría la clase abstracta, al menos cuando realmente significa "interfaz" (como en C ++). ¿Hay algún inconveniente oculto en este enfoque?
Febeling
1
@febeling, las clases abstractas, al menos en Java, no son solo interfaces. También definen un comportamiento (o la mayoría). Sin embargo, este enfoque podría ser bueno en algunos casos.
Dan Rosenstark
Necesito una clase base para implementar cierta funcionalidad que todas mis subclases comparten (eliminar la duplicación), pero también necesito un protocolo para otros métodos que la clase base no debe manejar (la parte abstracta). Así que necesito usar ambos, y ahí es donde puede ser difícil asegurarse de que sus subclases se implementen correctamente.
LightningStryk
19

Este hilo es algo antiguo, y la mayor parte de lo que quiero compartir ya está aquí.

Sin embargo, mi método favorito no se menciona, y AFAIK no hay soporte nativo en el Clang actual, así que aquí voy ...

En primer lugar, y sobre todo (como ya han señalado otros) las clases abstractas son algo muy poco común en Objective-C: en su lugar, usamos composición (a veces por delegación). Esta es probablemente la razón por la cual dicha característica aún no existe en el lenguaje / compilador, aparte de@dynamic propiedades, que IIRC se han agregado en ObjC 2.0 que acompaña a la introducción de CoreData.

Pero dado que (¡después de una evaluación cuidadosa de su situación!) Ha llegado a la conclusión de que la delegación (o composición en general) no es adecuada para resolver su problema, así es como lo hago:

  1. Implemente todos los métodos abstractos en la clase base.
  2. Haz esa implementación [self doesNotRecognizeSelector:_cmd]; ...
  3. …seguido por __builtin_unreachable(); silenciar la advertencia que obtendrá para los métodos no nulos, diciéndole "el control alcanzó el final de la función no nula sin retorno".
  4. Combine los pasos 2. y 3. en una macro, o anote -[NSObject doesNotRecognizeSelector:]utilizando __attribute__((__noreturn__))una categoría sin implementación para no reemplazar la implementación original de ese método e incluya el encabezado para esa categoría en la PCH de su proyecto.

Personalmente prefiero la versión macro, ya que me permite reducir la repetitiva tanto como sea posible.

Aquí está:

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

Como puede ver, la macro proporciona la implementación completa de los métodos abstractos, reduciendo la cantidad necesaria de repeticiones a un mínimo absoluto.

Una opción aún mejor sería presionar al equipo de Clang para que proporcione un atributo del compilador para este caso, a través de solicitudes de funciones. (Mejor, porque esto también permitiría diagnósticos en tiempo de compilación para aquellos escenarios en los que subclases, por ejemplo, NSIncrementalStore).

Por qué elijo este método

  1. Se hace el trabajo de manera eficiente y algo conveniente.
  2. Es bastante fácil de entender. (Bueno, eso __builtin_unreachable()puede sorprender a la gente, pero también es bastante fácil de entender).
  3. No se puede eliminar en las versiones de lanzamiento sin generar otras advertencias o errores del compilador, a diferencia de un enfoque que se basa en una de las macros de aserción.

Ese último punto necesita alguna explicación, supongo:

Algunas personas (¿la mayoría?) Eliminan las afirmaciones en las versiones de lanzamiento. (No estoy de acuerdo con ese hábito, pero esa es otra historia ...) No implementar un método requerido, sin embargo, es malo , terrible , incorrecto y, básicamente, el final del universo para su programa. Su programa no puede funcionar correctamente a este respecto porque no está definido, y el comportamiento indefinido es lo peor que existe. Por lo tanto, ser capaz de eliminar esos diagnósticos sin generar nuevos diagnósticos sería completamente inaceptable.

Ya es bastante malo que no pueda obtener diagnósticos adecuados en tiempo de compilación para tales errores de programador, y tenga que recurrir al descubrimiento en tiempo de ejecución para estos, pero si puede cubrirlo en versiones de lanzamiento, ¿por qué intentar tener una clase abstracta en el ¿primer lugar?

danyowdee
fuente
Esta es mi nueva solución favorita: la __builtin_unreachable();gema hace que esto funcione perfectamente. La macro hace que se documente automáticamente y el comportamiento coincide con lo que sucede si llama a un método faltante en un objeto.
Alex MDC
12

Usando @propertyy @dynamictambién podría funcionar. Si declara una propiedad dinámica y no proporciona una implementación de método coincidente, todo se compilará sin advertencias, y obtendrá un unrecognized selectorerror en tiempo de ejecución si intenta acceder a ella. Esto es esencialmente lo mismo que llamar [self doesNotRecognizeSelector:_cmd], pero con mucho menos tipeo.

Cameron Spickert
fuente
7

En Xcode (usando clang, etc.) me gusta usar __attribute__((unavailable(...)))para etiquetar las clases abstractas para que obtenga un error / advertencia si intenta usarlo.

Proporciona cierta protección contra el uso accidental del método.

Ejemplo

En la @interfaceetiqueta de la clase base, los métodos "abstractos":

- (void)myAbstractMethod:(id)param1 __attribute__((unavailable("You should always override this")));

Dando un paso más allá, creo una macro:

#define UnavailableMacro(msg) __attribute__((unavailable(msg)))

Esto te permite hacer esto:

- (void)myAbstractMethod:(id)param1 UnavailableMacro(@"You should always override this");

Como dije, esta no es una protección real del compilador, pero es tan buena como la que obtendrás en un lenguaje que no admite métodos abstractos.

Richard Stelling
fuente
1
No pude conseguirlo. He aplicado su sugerencia en mi clase base -init método y ahora Xcode no me permite crear una instancia de la clase heredada y se produce un error de tiempo de compilación disponible ... . ¿Podría explicar más?
anonim
Asegúrese de tener un -initmétodo en su subclase.
Richard Stelling
2
Es lo mismo que NS_UNAVAILABLE que activará un error cada vez que intente llamar al método marcado con dicho atributo. No veo cómo se puede usar en la clase abstracta.
Ben Sinclair
7

La respuesta a la pregunta se encuentra dispersa en los comentarios debajo de las respuestas ya dadas. Entonces, solo estoy resumiendo y simplificando aquí.

Opción 1: Protocolos

Si desea crear una clase abstracta sin implementación, use 'Protocolos'. Las clases que heredan un protocolo están obligadas a implementar los métodos en el protocolo.

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

Opción 2: Patrón de método de plantilla

Si desea crear una clase abstracta con implementación parcial como "Patrón de método de plantilla", entonces esta es la solución. Objetivo-C: ¿patrón de métodos de plantilla?

hadaytullah
fuente
6

Otra alternativa

Simplemente marque la clase en la clase Resumen y Afirmar o Excepción, lo que quiera.

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

Esto elimina la necesidad de anular init

bigkm
fuente
¿Sería eficiente simplemente devolver nulo? ¿O eso provocaría una excepción / otro error?
user2277872
Bueno, esto es solo para atrapar a los programadores para usar la clase directamente, lo que creo que hace un buen uso de una afirmación.
bigkm
5

(más de una sugerencia relacionada)

Quería tener una manera de informar al programador "no llamar desde el niño" y anularlo por completo (en mi caso, todavía ofrezco alguna funcionalidad predeterminada en nombre del padre cuando no está extendido):

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

La ventaja es que el programador VERÁ la "anulación" en la declaración y sabrá que no debe llamar [super ..].

De acuerdo, es feo tener que definir tipos de retorno individuales para esto, pero sirve como una pista visual lo suficientemente buena y no se puede usar fácilmente la parte "override_" en una definición de subclase.

Por supuesto, una clase aún puede tener una implementación predeterminada cuando una extensión es opcional. Pero como dicen las otras respuestas, implemente una excepción de tiempo de ejecución cuando sea apropiado, como para las clases abstractas (virtuales).

Sería bueno tener sugerencias compiladas como esta, incluso sugerencias para cuando es mejor pre / post llamar al implemento del super, en lugar de tener que buscar comentarios / documentación o ... asumir.

ejemplo de la pista

Ivan Dossev
fuente
4

Si está acostumbrado a que el compilador capture violaciones de creación de instancias abstractas en otros lenguajes, entonces el comportamiento de Objective-C es decepcionante.

Como lenguaje de enlace tardío, está claro que Objective-C no puede tomar decisiones estáticas sobre si una clase es realmente abstracta o no (podría estar agregando funciones en tiempo de ejecución ...), pero para casos de uso típicos esto parece una deficiencia. Preferiría que el compilador de plano evitara las instancias de clases abstractas en lugar de arrojar un error en tiempo de ejecución.

Aquí hay un patrón que estamos usando para obtener este tipo de verificación estática usando un par de técnicas para ocultar los inicializadores:

//
//  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
Cruzar_
fuente
3

Probablemente este tipo de situaciones solo deberían ocurrir en el momento del desarrollo, por lo que esto podría funcionar:

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

Puede usar un método propuesto por @Yar (con alguna modificación):

#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()

Aquí recibirá un mensaje como:

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

O afirmación:

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

En este caso obtendrá:

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

También puede usar protocolos y otras soluciones, pero esta es una de las más simples.

gbk
fuente
2

El cacao no proporciona nada llamado resumen. Podemos crear un resumen de clase que se verifica solo en tiempo de ejecución, y en tiempo de compilación esto no se verifica.

Hussain Shabbir
fuente
1

Por lo general, simplemente deshabilito el método init en una clase que quiero abstraer:

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

Esto generará un error en tiempo de compilación cada vez que llame a init en esa clase. Luego uso métodos de clase para todo lo demás.

Objective-C no tiene forma incorporada para declarar clases abstractas.

Ali
fuente
Pero recibo un error cuando llamo [super init] desde la clase derivada de esa clase abstracta. ¿Cómo resolver eso?
Sahil Doshi
@SahilDoshi Supongo que esa es la desventaja de usar este método. Lo uso cuando no quiero permitir que se instancia una clase, y nada hereda de esa clase.
Ali
Sí, lo entendí. pero su solución ayudará a crear algo como la clase singleton donde no queremos que nadie llame a init. Según mi conocimiento en el caso de la clase abstracta, alguna clase la heredará. de lo contrario, ¿cuál es el uso de la clase abstracta?
Sahil Doshi
0

Cambiando un poco lo que @redfood sugirió al aplicar el comentario de @ dotToString, en realidad tienes la solución adoptada por IGListKit de Instagram .

  1. Cree un protocolo para todos los métodos que no tienen sentido que se definan en la clase base (abstracta), es decir, que necesitan implementaciones específicas en los hijos.
  2. Cree una clase base (abstracta) que no implemente este protocolo. Puede agregar a esta clase cualquier otro método que tenga sentido para tener una implementación común.
  3. En cualquier parte de su proyecto, si un AbstractClassmétodo secundario debe ser ingresado o generado por algún método, escríbalo como en su AbstractClass<Protocol>lugar.

Como AbstractClassno se implementa Protocol, la única forma de tener una AbstractClass<Protocol>instancia es mediante subclases. Como AbstractClasssolo no se puede usar en ninguna parte del proyecto, se vuelve abstracto.

Por supuesto, esto no impide que los desarrolladores no aconsejados agreguen nuevos métodos que se refieren simplemente a AbstractClass, lo que terminaría permitiendo una instancia de la clase abstracta (ya no).

Ejemplo del mundo real: IGListKit tiene una clase base IGListSectionControllerque no implementa el protocolo IGListSectionType, sin embargo, cada método que requiere una instancia de esa clase, realmente pregunta por el tipo IGListSectionController<IGListSectionType>. Por lo tanto, no hay forma de usar un objeto de tipo IGListSectionControllerpara nada útil en su marco.

Gobe
fuente
0

De hecho, Objective-C no tiene clases abstractas, pero puede usar protocolos para lograr el mismo efecto. Aquí está la muestra:

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
fuente
0

Un ejemplo simple de crear una clase abstracta

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

¿No puedes simplemente crear un delegado?

Un delegado es como una clase base abstracta en el sentido de que usted dice qué funciones deben definirse, pero en realidad no las define.

Luego, cada vez que implemente su delegado (es decir, clase abstracta), el compilador le advierte de las funciones opcionales y obligatorias para las que necesita definir el comportamiento.

Esto me suena como una clase base abstracta.

usuario1709076
fuente