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?
objective-c
abstract-class
Jonathan Arbogast
fuente
fuente
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:
Si su método devuelve un valor, es un poco más fácil de usar
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.
fuente
@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:
También puedes hacer esto para init.
fuente
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:
entonces en tus métodos puedes usar esto
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 deNSInternalInconsistencyException
) ya que eso es lo que arroja el sistema de tiempo de ejecución en respuesta adoesNotRecognizeSelector
ser llamado (verNSObject
documentos).fuente
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 obtengaHomeViewController - method not implemented
donde HomeViewController heredó de Base - esto dará más informaciónLa 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.
fuente
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...
También hago lo siguiente para evitar la inicialización de la clase abstracta a través del inicializador predeterminado.
fuente
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:
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.
fuente
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á:
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
__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?
fuente
__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á ununrecognized 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.fuente
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
@interface
etiqueta de la clase base, los métodos "abstractos":Dando un paso más allá, creo una macro:
Esto te permite hacer esto:
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.
fuente
-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í.
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.
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?
fuente
Otra alternativa
Simplemente marque la clase en la clase Resumen y Afirmar o Excepción, lo que quiera.
Esto elimina la necesidad de anular
init
fuente
(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):
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.
fuente
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:
fuente
Probablemente este tipo de situaciones solo deberían ocurrir en el momento del desarrollo, por lo que esto podría funcionar:
fuente
Puede usar un método propuesto por @Yar (con alguna modificación):
Aquí recibirá un mensaje como:
O afirmación:
En este caso obtendrá:
También puede usar protocolos y otras soluciones, pero esta es una de las más simples.
fuente
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.
fuente
Por lo general, simplemente deshabilito el método init en una clase que quiero abstraer:
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.
fuente
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 suAbstractClass<Protocol>
lugar.Como
AbstractClass
no se implementaProtocol
, la única forma de tener unaAbstractClass<Protocol>
instancia es mediante subclases. ComoAbstractClass
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 protocoloIGListSectionType
, sin embargo, cada método que requiere una instancia de esa clase, realmente pregunta por el tipoIGListSectionController<IGListSectionType>
. Por lo tanto, no hay forma de usar un objeto de tipoIGListSectionController
para nada útil en su marco.fuente
De hecho, Objective-C no tiene clases abstractas, pero puede usar protocolos para lograr el mismo efecto. Aquí está la muestra:
CustomProtocol.h
TestProtocol.h
TestProtocol.m
fuente
Un ejemplo simple de crear una clase abstracta
fuente
¿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.
fuente