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?
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?
Respuestas:
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.
@protocol
definición, pero no puede definir los métodos allí.
+ (instancetype)exceptionForCallingAbstractMethod:(SEL)selector
esto funciona muy bien.
doesNotRecognizeSelector
.
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.
NSObject
referencia 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 :)
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 doesNotRecognizeSelector
ser llamado (ver NSObject
documentos).
universe.thing
pero se expande a [Universe universe].thing
. Gran diversión, ahorrando miles de letras de código ...
#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]
.
__PRETTY_FUNCTION__
todo el lugar, a través de una macro DLog (...), como se sugiere aquí: stackoverflow.com/a/969291/38557 .
#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()
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 implemented
donde HomeViewController heredó de Base - esto dará más información
La solución que se me ocurrió es:
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.
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.
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;
}
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.
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:
[self doesNotRecognizeSelector:_cmd];
...__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".-[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).
__builtin_unreachable()
puede sorprender a la gente, pero también es bastante fácil de entender).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?
__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.
Usando @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 compilará sin advertencias, y obtendrá un unrecognized selector
error 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.
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.
En la @interface
etiqueta 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.
-init
método en su subclase.
La respuesta a la pregunta se encuentra dispersa en los comentarios debajo de las respuestas ya dadas. Entonces, solo estoy resumiendo y simplificando aquí.
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
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?
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
(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.
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
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.
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.
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.
Cambiando un poco lo que @redfood sugirió al aplicar el comentario de @ dotToString, en realidad tienes la solución adoptada por IGListKit de Instagram .
AbstractClass
método secundario debe ser ingresado o generado por algún método, escríbalo como en su AbstractClass<Protocol>
lugar.Como AbstractClass
no se implementa Protocol
, la única forma de tener una AbstractClass<Protocol>
instancia es mediante subclases. Como AbstractClass
solo 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 IGListSectionController
que 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 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:
#import <Foundation/Foundation.h>
@protocol CustomProtocol <NSObject>
@required
- (void)methodA;
@optional
- (void)methodB;
@end
#import <Foundation/Foundation.h>
#import "CustomProtocol.h"
@interface TestProtocol : NSObject <CustomProtocol>
@end
#import "TestProtocol.h"
@implementation TestProtocol
- (void)methodA
{
NSLog(@"methodA...");
}
- (void)methodB
{
NSLog(@"methodB...");
}
@end
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
¿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.