¿Es posible hacer que el método de entrada sea privado en Objective-C?

Necesito ocultar (hacer privado) el método -init de mi clase en Objective-C.

¿Cómo puedo hacer eso?

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.

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 comstackción en cualquier llamada a init.

 -(instancetype) init __attribute__((unavailable("init not available"))); 

tiempo de compilación error

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 comstackdor 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 comstackción en Xcode. Específicamente, esto se establece en varios de sus archivos de cabecera HealthKit (HKUnit es uno de ellos).

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).

Pon esto en el archivo de encabezado

 - (id)init UNAVAILABLE_ATTRIBUTE; 

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 comstackdor (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 comstackció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 comstackdor … 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

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 Categoría 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 comstackdor emitirá advertencias si alguien intenta hacer esto.

MyClass.m

 @interface MyClass (PrivateMethods) - (NSString*) init; @end @implementation MyClass - (NSString*) init { // code... } @end 

Hay un hilo decente en MacRumors.com sobre este tema.

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 

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.