Necesito ocultar (hacer privado) el -init
método de mi clase en Objective-C.
¿Cómo puedo hacer eso?
objective-c
lajos
fuente
fuente
NS_UNAVAILABLE
. En general, le recomiendo que utilice este enfoque. ¿El OP consideraría revisar su respuesta aceptada? Las otras respuestas aquí proporcionan muchos detalles útiles, pero no son el método preferido para lograrlo.NS_UNAVAILABLE
aún permite que una persona que llama invoqueinit
indirectamente a través denew
. Simplemente anular elinit
retornonil
manejará ambos casos.Respuestas:
Objective-C, como Smalltalk, no tiene concepto de métodos "privados" versus métodos "públicos". Cualquier mensaje puede enviarse a cualquier objeto en cualquier momento.
Lo que puede hacer es arrojar un
NSInternalInconsistencyException
si-init
se invoca su método:La otra alternativa, que probablemente sea mucho mejor en la práctica, es hacer
-init
algo sensato para su clase si es posible.Si está tratando de hacer esto porque está tratando de "asegurarse" de que se use un objeto singleton, no se moleste. Específicamente, no se moleste con el "override
+allocWithZone:
,-init
,-retain
,-release
" método de crear singletons. Prácticamente siempre es innecesario y solo agrega complicaciones para no tener una ventaja real significativa.En su lugar, simplemente escriba su código de manera que su
+sharedWhatever
método sea cómo accede a un singleton y documente eso como la forma de obtener la instancia de singleton en su encabezado. Eso debería ser todo lo que necesita en la gran mayoría de los casos.fuente
alloc
yinit
y tienen su función de código de forma incorrecta, ya que tienen la clase correcta, pero la instancia equivocada. Esta es la esencia del principio de encapsulación en OO. Esconde cosas en sus API a las que otras clases no deberían necesitar u obtener acceso. No solo mantienes todo público y esperas que los humanos hagan un seguimiento de todo.NS_UNAVAILABLE
Esta es una versión corta del atributo no disponible. Apareció por primera vez en macOS 10.7 y 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 el código ObjC:
unavailable
Agregue el
unavailable
atributo al encabezado para generar un error del compilador en cualquier llamada a init.Si no tiene una razón, simplemente escriba
__attribute__((unavailable))
, o incluso__unavailable
:doesNotRecognizeSelector:
Se usa
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".NSAssert
Use
NSAssert
para lanzar NSInternalInconsistencyException y mostrar un mensaje:raise:format:
Use
raise:format:
para lanzar su propia excepción:[self release]
es necesario porque el objeto ya estabaalloc
alterado. 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 intencionalmente la ejecución.objc_designated_initializer
En caso de que tenga la intención de deshabilitar
init
para forzar el uso de un inicializador designado, hay un atributo para eso:Esto genera una advertencia a menos que cualquier otro método inicializador llame
myOwnInit
internamente. Los detalles se publicarán en Adopting Modern Objective-C después del próximo lanzamiento de Xcode (supongo).fuente
init
. Dado que, si este método no es válido, ¿por qué inicializa un objeto? Además, al lanzar una excepción, podrá especificar algún mensaje personalizado que comunique elinit*
método correcto al desarrollador, mientras que no tiene esa opción en caso dedoesNotRecognizeSelector
.Apple ha comenzado a usar lo siguiente en sus archivos de encabezado para deshabilitar el constructor init:
Esto se muestra correctamente como un error del compilador en Xcode. Específicamente, esto se establece en varios de sus archivos de encabezado HealthKit (HKUnit es uno de ellos).
fuente
Si está hablando del método predeterminado de inicio, entonces no puede. Se hereda de NSObject y todas las clases responderán sin advertencias.
Puede crear un nuevo método, digamos -initMyClass, y ponerlo en una categoría privada como Matt sugiere. Luego defina el método predeterminado -init para generar una excepción si se llama o (mejor) llame a su privado -initMyClass con algunos valores predeterminados.
Una de las razones principales por las que las personas parecen querer ocultar init es por los objetos singleton . Si ese es el caso, entonces no necesita ocultar -init, solo devuelva el objeto singleton en su lugar (o créelo si aún no existe).
fuente
Pon esto en el archivo de encabezado
fuente
Puede declarar que cualquier método no está disponible usando
NS_UNAVAILABLE
.Entonces puedes poner estas líneas debajo de tu @interface
Aún mejor defina una macro en su encabezado de prefijo
y
fuente
Eso depende de lo que quieras 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 a un cliente llamar 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, aún se ejecutará en 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 realmente no impedirá que se
init
ejecuten llamadas a , pero el compilador emitirá advertencias si alguien intenta hacer esto.MyClass.m
Hay un hilo decente en MacRumors.com sobre este tema.
fuente
bueno, el problema por el que no puedes hacerlo "privado / invisible" es porque el método init se envía a id (ya que alloc devuelve un id) no a YourClass
Tenga en cuenta que desde el punto del compilador (verificador) una identificación podría responder de manera potente a cualquier cosa que se haya escrito (no puede verificar lo que realmente entra en la identificación en tiempo de ejecución), por lo que puede ocultar init solo cuando nada en ninguna parte lo haría (publicly = in encabezado) use un método init, de lo que la compilación sabría, que no hay forma de que la identificación responda a init, ya que no hay init en ningún lugar (en su fuente, todas las bibliotecas, etc.)
por lo que no puede prohibir al usuario que pase init y que el compilador lo rompa ... pero lo que puede 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 alguien más no obtendrá (como initOnce, initWithSpecial ...)
Nota: que alguien podría hacer esto
y de hecho devolvería una nueva instancia, pero si initOnce no se declararía públicamente en ninguna parte de nuestro proyecto (en el encabezado), generaría una advertencia (la identificación podría no responder ...) y de todos modos la persona que usa esto, necesitaría saber exactamente que el inicializador real es initOnce
podríamos evitar esto aún más, pero no hay necesidad
fuente
Tengo que mencionar que colocar afirmaciones y plantear excepciones para ocultar métodos en la subclase tiene una trampa desagradable para los bien intencionados.
Recomendaría usar
__unavailable
como Jano explicó para su primer ejemplo .Los métodos pueden anularse 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á como se esperaba. 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 una implementación tan común:
Imagina lo que sucede con -initWithLessParameters, si hago esto en la subclase:
Esto implica que debe tender a utilizar métodos privados (ocultos), especialmente en los métodos de inicialización, a menos que planee anularlos. Pero, este es otro tema, ya que no siempre tiene el 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 ser anulados en subclases. (Los métodos "abstractos" como en Crear una clase abstracta en Objective-C )
Y no te olvides del método + nueva clase.
fuente