objective-c - tutorial - objective c ultima version
¿Es posible hacer que el método de entrada sea privado en Objective-C? (9)
Necesito ocultar (hacer privado) el método -init
de mi clase en Objective-C.
¿Cómo puedo hacer eso?
NS_UNAVAILABLE
- (instancetype)init NS_UNAVAILABLE;
Esta es una versión corta del atributo no disponible. Apareció por primera vez en macOS 10.7 e iOS 5 . Se define en NSObjCRuntime.h como #define NS_UNAVAILABLE UNAVAILABLE_ATTRIBUTE
.
Hay una versión que deshabilita el método solo para clientes Swift , no para código ObjC:
- (instancetype)init NS_SWIFT_UNAVAILABLE;
unavailable
Agregue el atributo unavailable
al encabezado para generar un error de compilación en cualquier llamada a init.
-(instancetype) init __attribute__((unavailable("init not available")));
Si no tiene una razón, simplemente escriba __attribute__((unavailable))
, o incluso __unavailable
:
-(instancetype) __unavailable init;
doesNotRecognizeSelector:
Use doesNotRecognizeSelector:
para generar una NSInvalidArgumentException. "El sistema de tiempo de ejecución invoca este método cada vez que un objeto recibe un mensaje aSelector al que no puede responder o reenviar".
- (instancetype) init {
[self release];
[super doesNotRecognizeSelector:_cmd];
return nil;
}
NSAssert
Utilice NSAssert
para lanzar NSInternalInconsistencyException y muestre un mensaje:
- (instancetype) init {
[self release];
NSAssert(false,@"unavailable, use initWithBlah: instead");
return nil;
}
raise:format:
Use raise:format:
para lanzar su propia excepción:
- (instancetype) init {
[self release];
[NSException raise:NSGenericException
format:@"Disabled. Use +[[%@ alloc] %@] instead",
NSStringFromClass([self class]),
NSStringFromSelector(@selector(initWithStateDictionary:))];
return nil;
}
[self release]
es necesario porque el objeto ya estaba alloc
. Cuando use ARC, el compilador lo llamará por usted. En cualquier caso, no es algo de lo que preocuparse cuando está a punto de detener la ejecución intencionalmente.
objc_designated_initializer
En caso de que intente desactivar init
para forzar el uso de un inicializador designado, hay un atributo para eso:
-(instancetype)myOwnInit NS_DESIGNATED_INITIALIZER;
Esto genera una advertencia a menos que cualquier otro método de inicialización llame a myOwnInit
internamente. Los detalles se publicarán en Adopting Modern Objective-C después del próximo lanzamiento de Xcode (supongo).
Apple ha comenzado a utilizar lo siguiente en sus archivos de cabecera para deshabilitar el constructor init:
- (instancetype)init NS_UNAVAILABLE;
Esto se muestra correctamente como un error de compilación en Xcode. Específicamente, esto se establece en varios de sus archivos de cabecera HealthKit (HKUnit es uno de ellos).
Debo mencionar que colocar aserciones y plantear excepciones para ocultar métodos en la subclase tiene una trampa desagradable para los bien intencionados.
Recomendaría usar __unavailable
como explicó Jano para su primer ejemplo .
Los métodos pueden ser anulados en subclases. Esto significa que si un método en la superclase usa un método que solo genera una excepción en la subclase, probablemente no funcionará según lo previsto. En otras palabras, acabas de romper lo que solía funcionar. Esto también es cierto con los métodos de inicialización. Aquí hay un ejemplo de tal implementación bastante común:
- (SuperClass *)initWithParameters:(Type1 *)arg1 optional:(Type2 *)arg2
{
...bla bla...
return self;
}
- (SuperClass *)initWithLessParameters:(Type1 *)arg1
{
self = [self initWithParameters:arg1 optional:DEFAULT_ARG2];
return self;
}
Imagine lo que le sucede a -initWithLessParameters, si hago esto en la subclase:
- (SubClass *)initWithParameters:(Type1 *)arg1 optional:(Type2 *)arg2
{
[self release];
[super doesNotRecognizeSelector:_cmd];
return nil;
}
Esto implica que debe tender a utilizar métodos privados (ocultos), especialmente en los métodos de inicialización, a menos que planee tener los métodos anulados. Pero este es otro tema, ya que no siempre tienes control total en la implementación de la superclase. (Esto me hace cuestionar el uso de __attribute ((objc_designated_initializer)) como mala práctica, aunque no lo he usado en profundidad.
También implica que puede usar aserciones y excepciones en métodos que deben anularse en subclases. (Los métodos "abstractos" como en Crear una clase abstracta en el Objetivo-C )
Y no te olvides del + nuevo método de clase.
Eso depende de lo que quiere decir con "hacer privado". En Objective-C, llamar a un método en un objeto podría describirse mejor como enviar un mensaje a ese objeto. No hay nada en el lenguaje que prohíba que un cliente llame a un método dado en un objeto; lo mejor que puede hacer es no declarar el método en el archivo de encabezado. Sin embargo, si un cliente llama al método "privado" con la firma correcta, se ejecutará en el tiempo de ejecución.
Dicho esto, la forma más común de crear un método privado en Objective-C es crear una Category en el archivo de implementación y declarar todos los métodos "ocultos" allí. Recuerde que esto no evitará que las llamadas a init
ejecuten, pero el compilador emitirá advertencias si alguien intenta hacer esto.
MyClass.m
@interface MyClass (PrivateMethods)
- (NSString*) init;
@end
@implementation MyClass
- (NSString*) init
{
// code...
}
@end
Hay un thread decente en MacRumors.com sobre este tema.
Objective-C, como Smalltalk, no tiene ningún concepto de métodos "privados" versus "públicos". Cualquier mensaje puede ser enviado a cualquier objeto en cualquier momento.
Lo que puede hacer es lanzar una NSInternalInconsistencyException
si se invoca su método -init
:
- (id)init {
[self release];
@throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:@"-init is not a valid initializer for the class Foo"
userInfo:nil];
return nil;
}
La otra alternativa, que probablemente sea mucho mejor en la práctica, es hacer algo que sea sensato para su clase si es posible.
Si está tratando de hacer esto porque está tratando de "asegurar" que se utiliza un objeto singleton, no se moleste. Específicamente, no se moleste con el +allocWithZone:
"override +allocWithZone:
-init
, -retain
, -retain
" para crear singletons. Prácticamente siempre es innecesario y solo agrega complicaciones sin una ventaja real significativa.
En su lugar, simplemente escriba su código de modo que su método +sharedWhatever
sea la forma de acceder a un singleton, y documente eso como la forma de obtener la instancia singleton en su encabezado. Eso debería ser todo lo que necesita en la gran mayoría de los casos.
Pon esto en el archivo de encabezado
- (id)init UNAVAILABLE_ATTRIBUTE;
Puede declarar que ningún método no está disponible utilizando NS_UNAVAILABLE
.
Entonces puedes poner estas líneas debajo de tu interfaz @
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
Mejor aún, defina una macro en su encabezado de prefijo
#define NO_INIT /
- (instancetype)init NS_UNAVAILABLE; /
+ (instancetype)new NS_UNAVAILABLE;
y
@interface YourClass : NSObject
NO_INIT
// Your properties and messages
@end
Si está hablando sobre el método de entrada predeterminado, entonces no puede. Se hereda de NSObject y todas las clases responderán sin advertencias.
Puede crear un nuevo método, decir -initMyClass, y ponerlo en una categoría privada como sugiere Matt. A continuación, defina el método de entrada predeterminado para generar una excepción si se llama o (mejor) llamar a su privado -initMyClass con algunos valores predeterminados.
Una de las razones principales por las que las personas parecen querer ocultar init es por objetos singleton . Si ese es el caso, entonces no necesita ocultar -init, simplemente devuelva el objeto singleton en su lugar (o créelo si aún no existe).
así el problema de por qué no puedes hacer que sea "privado / invisible" es porque el método init se envía a la identificación (como alloc devuelve una identificación) no a YourClass
Tenga en cuenta que, desde el punto del compilador (verificador), una identificación podría responder a cualquier cosa que se escriba (no puede verificar qué entra realmente en la identificación en tiempo de ejecución), por lo que podría ocultar init solo cuando nada (publicly = in encabezado) use un método init, que la compilación sabría, que no hay forma de que id responda a init, ya que no hay ningún init en ninguna parte (en su fuente, todas las bibliotecas, etc.)
así que no puedes prohibir al usuario que apruebe init y se haga añicos por el compilador ... pero lo que puedes hacer es evitar que el usuario obtenga una instancia real llamando a un init
simplemente implementando init, que devuelve nil y tiene un inicializador (privado / invisible) cuyo nombre no recibirá otro (como initOnce, initWithSpecial ...)
static SomeClass * SInstance = nil;
- (id)init
{
// possibly throw smth. here
return nil;
}
- (id)initOnce
{
self = [super init];
if (self) {
return self;
}
return nil;
}
+ (SomeClass *) shared
{
if (nil == SInstance) {
SInstance = [[SomeClass alloc] initOnce];
}
return SInstance;
}
Nota: que alguien podría hacer esto
SomeClass * c = [[SomeClass alloc] initOnce];
y de hecho devolvería una nueva instancia, pero si el initOnce en ningún lugar de nuestro proyecto fuera declarado públicamente (en el encabezado), generaría una advertencia (es posible que la identificación no responda ...) y, de todos modos, la persona que lo use necesitaría saber exactamente que el inicializador real es el initOnce
podríamos evitar esto aún más, pero no hay necesidad