Recibo la siguiente advertencia del compilador ARC:
"performSelector may cause a leak because its selector is unknown".
Esto es lo que estoy haciendo:
[_controller performSelector:NSSelectorFromString(@"someMethod")];
¿Por qué recibo esta advertencia? Entiendo que el compilador no puede verificar si el selector existe o no, pero ¿por qué eso causaría una fuga? ¿Y cómo puedo cambiar mi código para que ya no reciba esta advertencia?
ios
objective-c
memory-leaks
automatic-ref-counting
Eduardo Scoz
fuente
fuente
Respuestas:
Solución
El compilador está advirtiendo sobre esto por una razón. Es muy raro que esta advertencia simplemente se ignore, y es fácil de evitar. Así es cómo:
O más brevemente (aunque difícil de leer y sin el protector):
Explicación
Lo que está sucediendo aquí es que le está pidiendo al controlador el puntero de función C para el método correspondiente al controlador. Todos
NSObject
respondenmethodForSelector:
, pero también se puede usarclass_getMethodImplementation
en el tiempo de ejecución de Objective-C (útil si solo tiene una referencia de protocolo, comoid<SomeProto>
). Estos punteros de función se denominanIMP
s, y sontypedef
punteros de función ed simples (id (*IMP)(id, SEL, ...)
) 1 . Esto puede estar cerca de la firma del método real del método, pero no siempre coincidirá exactamente.Una vez que tenga el
IMP
, debe convertirlo en un puntero de función que incluya todos los detalles que ARC necesita (incluidos los dos argumentos ocultos implícitosself
y_cmd
de cada llamada al método Objective-C). Esto se maneja en la tercera línea (el(void *)
lado derecho simplemente le dice al compilador que usted sabe lo que está haciendo y que no genere una advertencia ya que los tipos de puntero no coinciden).Finalmente, llama al puntero de función 2 .
Ejemplo complejo
Cuando el selector toma argumentos o devuelve un valor, tendrá que cambiar un poco las cosas:
Razonamiento para la advertencia
La razón de esta advertencia es que con ARC, el tiempo de ejecución necesita saber qué hacer con el resultado del método al que está llamando. El resultado podría ser cualquier cosa:
void
,int
,char
,NSString *
,id
, etc. ARC normalmente recibe esta información del encabezado del tipo de objeto que está trabajando. 3En realidad, solo hay 4 cosas que ARC consideraría para el valor de retorno: 4
void
,int
, etc)init
/copy
family o atribuidos conns_returns_retained
)ns_returns_autoreleased
)La llamada a
methodForSelector:
supone que el valor de retorno del método al que está llamando es un objeto, pero no lo retiene / libera. Por lo tanto, podría terminar creando una fuga si se supone que su objeto se liberará como en el n. ° 3 anterior (es decir, el método al que llama devuelve un nuevo objeto).Para los selectores que intenta llamar a ese retorno
void
u otros objetos que no sean objetos, puede habilitar las funciones del compilador para ignorar la advertencia, pero puede ser peligroso. He visto a Clang pasar por algunas iteraciones de cómo maneja los valores de retorno que no están asignados a variables locales. No hay ninguna razón por la que con ARC habilitado no pueda retener y liberar el valor del objeto que se devuelvemethodForSelector:
aunque no quiera usarlo. Desde la perspectiva del compilador, es un objeto después de todo. Eso significa que si el método al que está llamandosomeMethod
devuelve un objeto no incluido (incluidovoid
), podría terminar con un valor de puntero de basura retenido / liberado y bloquearse.Argumentos adicionales
Una consideración es que esta es la misma advertencia que ocurrirá
performSelector:withObject:
y podría tener problemas similares al no declarar cómo ese método consume parámetros. ARC permite declarar los parámetros consumidos , y si el método consume el parámetro, es probable que finalmente envíe un mensaje a un zombie y se bloquee. Hay formas de evitar esto con la conversión en puente, pero realmente sería mejor simplemente usar laIMP
metodología de puntero y función anterior. Dado que los parámetros consumidos rara vez son un problema, es probable que esto no ocurra.Selectores Estáticos
Curiosamente, el compilador no se quejará de los selectores declarados estáticamente:
La razón de esto es porque el compilador realmente puede grabar toda la información sobre el selector y el objeto durante la compilación. No necesita hacer suposiciones sobre nada. (Revisé esto hace un año mirando la fuente, pero no tengo una referencia en este momento).
Supresión
Al tratar de pensar en una situación en la que sería necesaria la supresión de esta advertencia y un buen diseño del código, me quedo en blanco. Alguien comparta si han tenido una experiencia en la que fue necesario silenciar esta advertencia (y lo anterior no maneja las cosas correctamente).
Más
Es posible construir una
NSMethodInvocation
para manejar esto también, pero hacerlo requiere mucho más tipeo y también es más lento, por lo que hay pocas razones para hacerlo.Historia
Cuando la
performSelector:
familia de métodos se agregó por primera vez a Objective-C, ARC no existía. Al crear ARC, Apple decidió que se debería generar una advertencia para estos métodos como una forma de guiar a los desarrolladores hacia el uso de otros medios para definir explícitamente cómo se debe manejar la memoria al enviar mensajes arbitrarios a través de un selector con nombre. En Objective-C, los desarrolladores pueden hacer esto mediante el uso de conversiones de estilo C en punteros de función sin formato.Con la introducción de Swift, Apple ha documentado la
performSelector:
familia de métodos como "intrínsecamente inseguros" y no están disponibles para Swift.Con el tiempo, hemos visto esta progresión:
performSelector:
(gestión de memoria manual)performSelector:
performSelector:
y documenta estos métodos como "inherentemente inseguros"La idea de enviar mensajes basados en un selector con nombre no es, sin embargo, una característica "inherentemente insegura". Esta idea se ha utilizado con éxito durante mucho tiempo en Objective-C, así como en muchos otros lenguajes de programación.
1 Todos los métodos de Objective-C tienen dos argumentos ocultos,
self
y_cmd
que se añaden de forma implícita cuando se llama a un método.2 Llamar a una
NULL
función no es seguro en C. El protector utilizado para verificar la presencia del controlador asegura que tengamos un objeto. Por lo tanto, sabemos que conseguiremos unaIMP
demethodForSelector:
(aunque puede ser_objc_msgForward
, la entrada en el sistema de reenvío de mensajes). Básicamente, con la guardia en su lugar, sabemos que tenemos una función para llamar.3 En realidad, es posible que obtenga la información incorrecta si declara sus objetos como
id
y no está importando todos los encabezados. Podría terminar con bloqueos en el código que el compilador cree que está bien. Esto es muy raro, pero podría suceder. Por lo general, recibirá una advertencia de que no sabe cuál de las dos firmas de métodos elegir.4 Véase la referencia ARC en valores de retorno retenidos y los valores de retorno no retenidos para más detalles.
fuente
performSelector:
métodos no se implementan de esta manera. Tienen una firma de método estricta (devoluciónid
, toma uno o dosid
s), por lo que no es necesario manejar tipos primitivos.Cannot initialize a variable of type 'CGRect (*)(__strong id, SEL, CGRect, UIView *__strong)' with an rvalue of type 'void *'
cuando se usa el último Xcode. (5.1.1) ¡Aún así, aprendí mucho!void (*func)(id, SEL) = (void *)imp;
no se compila, lo he reemplazado convoid (*func)(id, SEL) = (void (*)(id, SEL))imp;
void (*func)(id, SEL) = (void *)imp;
a<…> = (void (*))imp;
o<…> = (void (*) (id, SEL))imp;
En el compilador LLVM 3.0 en Xcode 4.2 puede suprimir la advertencia de la siguiente manera:
Si obtiene el error en varios lugares y desea utilizar el sistema de macro C para ocultar los pragmas, puede definir una macro para que sea más fácil suprimir la advertencia:
Puede usar la macro de esta manera:
Si necesita el resultado del mensaje realizado, puede hacer esto:
fuente
pop
ypush
-pragmas es mucho más limpio y seguro.if ([_target respondsToSelector:_selector]) {
lógica o similar.Supongo que esto es esto: dado que el selector es desconocido para el compilador, ARC no puede exigir una administración de memoria adecuada.
De hecho, hay momentos en que la administración de memoria está vinculada al nombre del método por una convención específica. En concreto, estoy pensando en constructores de conveniencia frente a maquillaje métodos; el primero devuelve por convención un objeto lanzado automáticamente; este último un objeto retenido. La convención se basa en los nombres del selector, por lo que si el compilador no conoce el selector, no puede aplicar la regla de administración de memoria adecuada.
Si esto es correcto, creo que puede usar su código de manera segura, siempre que se asegure de que todo esté bien en cuanto a la administración de la memoria (por ejemplo, que sus métodos no devuelvan los objetos que asignan).
fuente
__attribute
método a cada método que explique su administración de memoria. Pero también hace que sea imposible para el cumplidor manejar adecuadamente este patrón (un patrón que solía ser muy común, pero que ha sido reemplazado por patrones más robustos en los últimos años).SEL
y asignar diferentes selectores dependiendo de la situación? Así se hace, lenguaje dinámico ...En la configuración de compilación de su proyecto , en Otros indicadores de advertencia (
WARNING_CFLAGS
), agregue-Wno-arc-performSelector-leaks
Ahora solo asegúrese de que el selector al que está llamando no haga que su objeto sea retenido o copiado.
fuente
Como solución alternativa hasta que el compilador permita anular la advertencia, puede usar el tiempo de ejecución
en vez de
Tendrás que
fuente
[_controller performSelector:NSSelectorFromString(@"someMethod")];
yobjc_msgSend(_controller, NSSelectorFromString(@"someMethod"));
no son equivalentes! Eche un vistazo a los desajustes de firma de método y una gran debilidad en la tipificación débil de Objective-C están explicando el problema en profundidad.Para ignorar el error solo en el archivo con el selector de ejecución, agregue un #pragma de la siguiente manera:
Esto ignoraría la advertencia en esta línea, pero aún lo permitiría durante el resto de su proyecto.
fuente
#pragma clang diagnostic warning "-Warc-performSelector-leaks"
. Sé que si apago una advertencia, me gusta volver a encenderla lo antes posible, por lo que no dejo que otra advertencia no anticipada se me escape accidentalmente. Es poco probable que esto sea un problema, pero es solo mi práctica cada vez que apago una advertencia.#pragma clang diagnostic warning push
antes de realizar cualquier cambio y#pragma clang diagnostic warning pop
restaurar el estado anterior. Útil si está desactivando cargas y no desea tener muchas líneas de pragma de reactivación en su código.Extraño pero cierto: si es aceptable (es decir, el resultado es nulo y no le importa dejar que el ciclo de ejecución una vez), agregue un retraso, incluso si esto es cero:
Esto elimina la advertencia, presumiblemente porque asegura al compilador que ningún objeto puede ser devuelto y de alguna manera mal administrado.
fuente
Aquí hay una macro actualizada basada en la respuesta dada anteriormente. Este debería permitirle ajustar su código incluso con una declaración de devolución.
fuente
return
no tiene que estar dentro de la macro;return SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING([_target performSelector:_action withObject:self]);
También funciona y se ve más sano.Este código no involucra indicadores del compilador o llamadas directas en tiempo de ejecución:
NSInvocation
permite establecer múltiples argumentos, de modo que, a diferencia deperformSelector
esto, funcionará en cualquier método.fuente
Bueno, hay muchas respuestas aquí, pero dado que esto es un poco diferente, combinando algunas respuestas, pensé que lo incluiría. Estoy usando una categoría NSObject que verifica para asegurarse de que el selector devuelva vacío, y también suprime el compilador advertencia.
fuente
Por el bien de la posteridad, he decidido tirar mi sombrero al ring :)
Recientemente he estado viendo más y más reestructuración lejos del paradigma
target
/selector
, a favor de cosas como protocolos, bloques, etc. Sin embargo, hay un reemplazo directoperformSelector
que he usado algunas veces:Estos parecen ser un reemplazo limpio, seguro para ARC y casi idéntico
performSelector
sin tener que preocuparse demasiadoobjc_msgSend()
.Sin embargo, no tengo idea si hay un análogo disponible en iOS.
fuente
[[UIApplication sharedApplication] sendAction: to: from: forEvent:]
. Lo examiné una vez, pero es un poco extraño usar una clase relacionada con la interfaz de usuario en el medio de su dominio o servicio solo para hacer una llamada dinámica. ¡Gracias por incluir esto!id
from-performSelector:...
to:
sea nulo, que no lo es. Simplemente va directamente al objeto de destino sin verificación previa. Entonces no hay "más gastos generales". No es una gran solución, pero la razón que das no es la razón. :)La respuesta de Matt Galloway en este hilo explica el por qué:
Parece que generalmente es seguro suprimir la advertencia si ignora el valor de retorno. No estoy seguro de cuál es la mejor práctica si realmente necesita obtener un objeto retenido de performSelector, aparte de "no haga eso".
fuente
@ c-road proporciona el enlace correcto con la descripción del problema aquí . A continuación puede ver mi ejemplo, cuando performSelector causa una pérdida de memoria.
El único método que causa pérdida de memoria en mi ejemplo es CopyDummyWithLeak. La razón es que ARC no sabe, que copySelector devuelve el objeto retenido.
Si ejecuta la herramienta Memory Leak Tool, puede ver la siguiente imagen: ... y no hay pérdidas de memoria en ningún otro caso:
fuente
Para hacer que la macro de Scott Thompson sea más genérica:
Entonces úsalo así:
fuente
¡No suprima las advertencias!
No hay menos de 12 soluciones alternativas para jugar con el compilador.
Si bien eres inteligente en el momento de la primera implementación, pocos ingenieros en la Tierra pueden seguir tus pasos, y este código finalmente se romperá.
Rutas seguras:
Todas estas soluciones funcionarán, con cierto grado de variación de su intención original. Suponga que
param
puede ser asínil
si así lo desea:Ruta segura, mismo comportamiento conceptual:
Ruta segura, comportamiento ligeramente diferente:
(Ver esta respuesta)
Use cualquier hilo en lugar de
[NSThread mainThread]
.Rutas peligrosas
Requiere algún tipo de silenciador del compilador, que seguramente se romperá. Tenga en cuenta que en la actualidad, se hizo alto en Swift .
fuente
performSelectorOnMainThread
es una buena manera de silenciar la advertencia y tiene efectos secundarios. (no resuelve la pérdida de memoria) El extra suprime explícitamente la advertencia de una manera muy clara.#clang diagnostic ignored
- (void)
método no es el verdadero problema.Debido a que está utilizando ARC, debe estar utilizando iOS 4.0 o posterior. Esto significa que podrías usar bloques. Si en lugar de recordar el selector para realizarlo, tomaste un bloqueo, ARC podría rastrear mejor lo que realmente está sucediendo y no tendrías que correr el riesgo de introducir accidentalmente una pérdida de memoria.
fuente
self
usaste a través de un ivar (por ejemplo, enivar
lugar deself->ivar
).En lugar de usar el enfoque de bloque, lo que me dio algunos problemas:
Usaré NSInvocation, así:
fuente
Si no necesita pasar ningún argumento, debe usar una solución fácil
valueForKeyPath
. Esto incluso es posible en unClass
objeto.fuente
También podría usar un protocolo aquí. Entonces, cree un protocolo como este:
En su clase que necesita llamar a su selector, entonces tiene una propiedad @.
Cuando necesite llamar
@selector(doSomethingWithObject:)
a una instancia de MyObject, haga esto:fuente