color change ios objective-c nsobject

ios - change - Cómo prohibir el método de inicio básico en un NSObject



ionic header text color (6)

Quiero forzar al usuario a usar mi propio método init (por ejemplo, -(id)initWithString:(NSString*)foo; ) y no el [[myObject alloc]init]; .

¿Cómo puedo hacer eso?


Todas las otras respuestas aquí están desactualizadas. ¡Hay una manera de hacerlo correctamente ahora!

Si bien es fácil bloquearse en el tiempo de ejecución cuando alguien llama a su método, la verificación en tiempo de compilación sería mucho más preferible.

Afortunadamente, esto ha sido posible en Objective-C por un tiempo.

Usando LLVM, puede declarar que cualquier método no está disponible en una clase como esta

- (void)aMethod __attribute__((unavailable("This method is not available")));

Esto hará que el compilador se queje cuando intente llamar a un aMethod . ¡Genial!

Dado que - (id)init es solo un método ordinario, puede prohibir llamar al inicializador predeterminado (o cualquier otro) de esta manera.

Tenga en cuenta, sin embargo, que esto no se asegurará contra el método que se utiliza utilizando los aspectos dinámicos del lenguaje, por ejemplo, a través de [object performSelector:@selector(aMethod)] etc. En el caso de init, ni siquiera obtendrá una advertencia, porque el método init está definido en otras clases, y el compilador no sabe lo suficiente como para darle una advertencia de selector no declarado.

Entonces, para asegurarte de esto, asegúrate de que el método init se bloquee cuando te llamen (consulta la respuesta de Adam ).

Si desea rechazar - (id)init en un marco, asegúrese de no permitir + (id)new , ya que esto solo se reenviará a init.

Javi Soto ha escrito una pequeña macro para prohibir el uso del inicializador designado más rápido y más fácil y para dar mensajes más agradables. Puedes encontrarlo here .


De hecho, voté por la respuesta de Adam, pero me gustaría agregarle algunas cosas.

Primero, se recomienda encarecidamente (como parece en los métodos de init generados automáticamente en NSObject subclases de NSObject ) que se marque a self contra de nil en los init s. Además, no creo que los objetos de clase tengan la garantía de ser "iguales" como en == . Hago esto mas como

- (id)init { NSAssert(NO, @"You are doing it wrong."); self = [super init]; if ([self isKindOfClass:[InitNotAllowedClass class]]) self = nil; return self; }

Tenga en cuenta que utilizo isKindOfClass: lugar, porque en mi humilde opinión, si esta clase no permite a init , no debería permitir que sus descendientes también la tengan. Si una de sus subclases lo quiere de vuelta (lo que no tiene sentido para mí), debería anularlo explícitamente llamando a mi inicializador designado.

Pero lo que es más importante, ya sea que adopte el enfoque anterior o no, siempre debe tener la documentación adecuada. Siempre debe indicar claramente qué método es su inicializador designado, intente lo mejor que pueda para recordar a los demás que no usen inicializadores inapropiados en la documentación, y confíe en otros usuarios / desarrolladores, en lugar de tratar de "salvar el culo de todos" con códigos inteligentes


La respuesta aceptada es incorrecta. PUEDES hacer esto, y es muy fácil, solo tienes que ser un poco explícito. Aquí hay un ejemplo:

Tienes una clase llamada "DontAllowInit", en la que deseas evitar que se inicien personas:

@implementation DontAllowInit - (id)init { if( [self class] == [DontAllowInit class]) { NSAssert(false, @"You cannot init this class directly. Instead, use a subclass e.g. AcceptableSubclass"); self = nil; // as per @uranusjr''s answer, should assign to self before returning } else self = [super init]; return nil; }

Explicación:

  1. Cuando llama a [super init], la clase que se asignó fue la SUBCLASS.
  2. "self" es la instancia , es decir, lo que se inició
  3. "[self class]" es la clase de la que se creó una instancia , que será SUBCLASS cuando SUBCLASS esté llamando a [super init], o será SUPERCLASS cuando se llame a SUPERCLASS con un simple [[asignación de SuperClass] init]
  4. Entonces, cuando la superclase recibe una llamada "init", solo necesita verificar si la clase asignada es la misma que su propia clase

Funciona perfectamente. NB: No recomiendo esta técnica para las "aplicaciones normales" porque, por lo general, INSTEAD desea utilizar un protocolo.

SIN EMBARGO ... cuando escribes bibliotecas ... esta técnica es MUY valiosa: con frecuencia deseas "guardar (a otros desarrolladores) de ellos mismos", y es fácil de NSAssert y decirles "¡Ups! Intentaste asignar / iniciar la clase equivocada ! Prueba la clase X en su lugar ... ".


Respuesta corta: no puedes.

Respuesta más larga: la mejor práctica es establecer su inicializador más detallado como el inicializador designado, como se describe here . ''init'' llamará a ese inicializador con valores predeterminados y sanos.

Otra opción es ''afirmar (0)'' o bloquearse de otra manera dentro de ''init'', pero esta no es una buena solución.


tl; Dr

Swift :

private init() {}

Como todas las clases de Swift incluyen un inicio interno por defecto, puede cambiarlo a privado para evitar que otras clases lo llamen.

C objetivo:

Pon esto en el archivo .h de tu clase.

- (instancetype)init NS_UNAVAILABLE;

Esto se basa en una definición de sistema operativo que impide que se llame al método nombrado.


-(id) init { @throw [NSException exceptionWithName: @"MyExceptionName" reason: @"-init is not allowed, use -initWithString: instead" userInfo: nil]; } -(id) initWithString: (NSString*) foo { self = [super init]; // OK because it calls NSObject''s init, not yours // etc

Lanzar la excepción se justifica si se documenta que -init no está permitido y, por lo tanto, usarlo es un error del programador. Sin embargo, una mejor respuesta sería hacer -init -initWtihString: con algún valor predeterminado adecuado, es decir

-(id) init { return [self initWithString: @""]; }