Creando una clase abstracta en Objective-C

Originalmente soy un progtwigdor 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?

Normalmente, la clase Objective-C es abstracta por convención solamente; si el autor documenta una clase como abstracta, simplemente no la use sin subclasificarla. Sin embargo, no existe una aplicación en tiempo de comstackción que impida la creación de instancias de una clase abstracta. De hecho, no hay nada que impida que un usuario proporcione implementaciones de métodos abstractos a través de una categoría (es decir, en tiempo de ejecución). Puede forzar a un usuario a anular al menos ciertos métodos al hacer 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]; 

como entonces, no necesita agregar una statement de retorno del método.

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

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

Puede simular una clase abstracta: haciendo que los métodos / selectores llamen a doesNotRecognizeSelector: y, por lo tanto, presente una excepción que haga que la clase no se pueda usar.

Por ejemplo:

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

También puedes hacer esto para init.

Simplemente, escriba la respuesta de @Barry Wark anterior (y la actualización para iOS 4.3) y deje 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 lo mantendré hasta que me enseñen 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 que se llama a doesNotRecognizeSelector (consulte los documentos de NSObject ).

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 quizás la llame abstracta) que implemente el protocolo. Para todos los métodos que desee “abstracto”, impleméntelos en el archivo .m, pero no en el archivo .h.
  3. Haga que su clase hija herede de la clase base Y implemente el protocolo.

De esta forma, el comstackdor le dará una advertencia sobre cualquier método del protocolo que no haya sido implementado por su clase secundaria.

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

De la lista de correo de Omni Group :

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

Entonces, lo único que debe hacer es definir la clase abstracta como cualquier otra clase normal e implementar los stubs de métodos para los métodos abstractos que, o bien están vacíos, o bien 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; } 

En lugar de tratar de 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)op { // do something with operation } 

Donde op puede ser cualquier objeto que implemente el protocolo de Operación.

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

Este hilo es algo viejo, y la mayoría 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 …

Primero, y más importante (como otros ya lo han señalado), las clases abstractas son algo muy poco común en el Objetivo-C: usualmente usamos la composición (a veces a través de la delegación). Esta es probablemente la razón por la cual tal característica no existe ya en el lenguaje / comstackdor, aparte de @dynamic propiedades @dynamic , que IIRC se ha agregado en ObjC 2.0 que acompañ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 la composición en general) no es adecuada para resolver su problema, así es como lo hago:

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

Personalmente, prefiero la versión macro ya que eso me permite reducir el texto repetitivo 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 texto repetitivo a un mínimo absoluto.

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

Por qué elijo este método

  1. El trabajo se realiza de manera eficiente y de manera conveniente.
  2. Es bastante fácil de entender. (De acuerdo, que __builtin_unreachable() puede sorprender a las personas, pero también es bastante fácil de entender.)
  3. No se puede eliminar en comstackciones de lanzamiento sin generar otras advertencias o compiler errors, a diferencia de un enfoque basado en una de las macros de aserción.

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

Algunas personas (¿la mayoría?) Quitan aserciones en versiones de lanzamiento. (No estoy de acuerdo con ese hábito, pero esa es otra historia …) Sin embargo, no implementar un método obligatorio es malo , terrible , erróneo y básicamente el fin del universo para su progtwig. Su progtwig no puede funcionar correctamente en este sentido porque no está definido, y el comportamiento indefinido es lo peor que haya pasado. Por lo tanto, ser capaz de eliminar esos diagnósticos sin generar nuevos diagnósticos sería completamente inaceptable.

Ya es suficientemente malo que no pueda obtener diagnósticos de tiempo de comstackción adecuados para tales errores de progtwigdor, y tenga que recurrir a descubrimiento en tiempo de ejecución para estos, pero si puede escalarlo en comstackciones de lanzamiento, ¿por qué intentar tener una clase abstracta en el ¿primer lugar?

Usar @property y @dynamic también podría funcionar. Si declara una propiedad dinámica y no proporciona una implementación de método coincidente, todo se comstackrá sin advertencias, y obtendrá un error de unrecognized selector en tiempo de ejecución si intenta acceder a él. Esto es esencialmente lo mismo que llamar [self doesNotRecognizeSelector:_cmd] , pero con mucho menos tipeo.

En Xcode (usando clang, etc.) me gusta usar __attribute__((unavailable(...))) para etiquetar las clases abstractas, por lo que obtienes un error / advertencia si tratas de usarlo.

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

Ejemplo

En la clase base @interface etiqueta los métodos “abstractos”:

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

Llevando esto 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 verdadera protección del comstackdor, pero es casi tan buena como la que obtendrás en un lenguaje que no admite métodos abstractos.

La respuesta a la pregunta está dispersa en los comentarios en 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”, esta es la solución. Objective-C – ¿Patrón de métodos de plantilla?

Otra alternativa

Simplemente marque la clase en la clase Abstracta y Assert o Exception, lo que sea que desee.

 @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

(más de una sugerencia relacionada)

Quería tener una forma de decirle al progtwigdor “no llamar desde el niño” y anularlo por completo (en mi caso, todavía ofrecer algunas funciones predeterminadas en nombre del padre cuando no se extiende):

 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 progtwigdor VERÁ la “anulación” en la statement y sabrá que no deberían estar llamando [super ..] .

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

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

Sería bueno haber incorporado consejos de comstackción como este, incluso pistas sobre cuándo es mejor pre / post llamar al implemento del súper, en lugar de tener que buscar entre comentarios / documentación o … asumir.

ejemplo de la pista

Si está acostumbrado a que el comstackdor detecte violaciones de creación de instancias abstractas en otros idiomas, 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 (puede que esté agregando funciones en tiempo de ejecución …), pero para casos de uso típicos esto parece una falla. Preferiría que el comstackdor impidiera la creación de instancias de clases abstractas en lugar de arrojar un error en el tiempo de ejecución.

Aquí hay un patrón que estamos usando para obtener este tipo de comprobació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  -(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 

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

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

Puede usar un método propuesto por @Yar (con algunas modificaciones):

 #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:

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

O aserción:

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

En este caso, obtendrás:

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

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

El cocoa no proporciona nada llamado abstracto. Podemos crear un resumen de clase que se comprueba solo en tiempo de ejecución, y en tiempo de comstackción no se verifica.

Normalmente desactivo 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 comstackció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 una forma incorporada para declarar clases abstractas.

Cambiando un poco lo que sugirió @redfood 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 para definirse en la clase base (abstracta), es decir, necesitan implementaciones específicas en los niños.
  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 lugar de su proyecto, si un hijo de AbstractClass debe ser ingresado o enviado por algún método, escríbalo como AbstractClass .

Debido a que AbstractClass no implementa el Protocol , la única forma de tener una instancia de AbstractClass es creando subclases. Como AbstractClass sí solo no se puede utilizar en ninguna parte del proyecto, se vuelve abstracto.

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

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

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

CustomProtocol.h

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

TestProtocol.h

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

TestProtocol.m

 #import "TestProtocol.h" @implementation TestProtocol - (void)methodA { NSLog(@"methodA..."); } - (void)methodB { NSLog(@"methodB..."); } @end 

Un simple ejemplo de crear una clase abstracta

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

¿No puedes simplemente crear un delegado?

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

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

Esto me parece una clase base abstracta.