objective-c ios xcode memory-management automatic-ref-counting

objective c - ¿El patrón de diseño Target-Action se convirtió en una mala práctica bajo ARC?



objective-c ios (4)

Durante años he estado siguiendo un gran patrón llamado Target-Action que dice así:

Un objeto llama a un selector especificado en un objeto de destino específico cuando llega el momento de llamar. Esto es muy útil en muchos casos diferentes en los que necesita una devolución de llamada simple a un método arbitrario.

Aquí hay un ejemplo:

- (void)itemLoaded { [specifiedReceiver performSelector:specifiedSelector]; }

Bajo ARC ahora resulta que hacer algo como esto de repente se volvió peligroso.

Xcode lanza una advertencia que dice así:

PerformSelector puede causar una fuga porque su selector es desconocido

Por supuesto, se desconoce el selector, ya que como parte del patrón de diseño Target-Action puede especificar el selector que desee para recibir una llamada cuando sucede algo interesante.

Lo que más me molesta acerca de esta advertencia es que dice que puede haber una pérdida potencial de memoria. Desde mi punto de vista, ARC no dobla las reglas de administración de memoria, sino que simplemente automatiza la inserción de mensajes de retención / liberación / liberación automática en las ubicaciones correctas.

Otra cosa a tener en cuenta aquí: -performSelector: tiene un valor de retorno de id . ARC analiza las firmas de métodos para descubrir mediante la aplicación de convenciones de nomenclatura si el método devuelve un +1 retener el objeto de recuento o no. En este caso, ARC no sabe si el selector es una fábrica de " -newFooBar " o simplemente llama a un método de trabajo no sospechoso (que casi siempre es el caso con Target-Action). En realidad, ARC debería haber reconocido que no esperaba un valor de retorno, y por lo tanto, olvidarme de cualquier potencial +1 de retención del valor de retorno contado. Mirándolo desde ese punto de vista, puedo ver de dónde viene el ARC, pero aún hay demasiada incertidumbre sobre lo que realmente significa en la práctica.

¿Significa eso ahora que bajo ARC algo puede ir mal, lo que nunca ocurriría sin ARC? No veo cómo esto podría producir una pérdida de memoria. ¿Puede alguien dar ejemplos de situaciones en las que es peligroso hacerlo y cómo se crea exactamente una fuga en ese caso?

Realmente busqué en Google el Internet, pero no encontré ningún sitio explicando por qué .


ARC lanza la advertencia porque no puede garantizar que el selector no esté creando un objeto que desconozca. En teoría, podría recibir algo de ese método que ARC no puede manejar:

id objectA = [someObject performSelector:@selector(createObjectA)];

Tal vez algún día pueda, pero en este momento no puede. (Tenga en cuenta que si conoce el objeto (no es una identificación) no arroja esta advertencia).

Si intentas simplemente ejecutar un método sin recibir un objeto de vuelta, te recomiendo usar objc_msgSend. Pero tienes que incluir en tu clase:

#include <objc/message.h> objc_msgSend(someObject, action);


Como se describió anteriormente, obtienes esa advertencia porque el compilador no sabe dónde (o si) colocar el retener / liberar el performSelector: valor de retorno.

Pero tenga en cuenta que si utiliza [someObject performSelector:@selector(selectorName)] no generará advertencias (al menos en Xcode 4.5 con llvm 4.1) porque el selector exacto es fácil de determinar (lo establece explícitamente) y es por eso que el compilador es capaz de poner los retenes / liberaciones en el lugar correcto.

Es por eso que recibirá una advertencia solo si pasa el selector usando el puntero SEL porque en ese caso el compilador no puede determinar en todo caso qué hacer. Entonces usando el siguiente

SEL s = nil; if(condition1) SEL = @selector(sel1) else SEL = @selector(sel2) [self performSelector:s];

generará una advertencia. Pero refactorizarlo para que sea:

if(condition1) [self performSelector:@selector(sel1)] else [self performSelector:@selector(sel2)]

no generará ninguna advertencia


El problema con performSelector es que ARC no sabe qué hace el selector que realizará. Considera lo siguiente:

id anotherObject1 = [someObject performSelector:@selector(copy)]; id anotherObject2 = [someObject performSelector:@selector(giveMeAnotherNonRetainedObject)];

Ahora, ¿cómo puede saber ARC que el primero devuelve un objeto con un conteo de retención de 1, pero el segundo devuelve un objeto que se libera automáticamente? (Estoy definiendo un método llamado giveMeAnotherNonRetainedObject aquí que devuelve algo lanzado automáticamente). Si no agregaba ningún lanzamiento, entonces otro anotherObject1 se anotherObject1 aquí.

Obviamente, en mi ejemplo, los selectores a realizar son realmente conocidos, pero imagine que fueron elegidos en tiempo de ejecución. ARC realmente no pudo hacer su trabajo de poner en el número correcto de retain o release aquí porque simplemente no sabe lo que el selector va a hacer. Tiene razón en que ARC no está doblando ninguna regla y solo está agregando las llamadas de administración de memoria correctas para usted, pero eso es precisamente lo que no puede hacer aquí.

Tiene razón en que el hecho de que esté ignorando el valor de retorno significa que va a estar bien, pero en general, ARC solo está siendo exigente y advirtiendo. Pero supongo que es por eso que es una advertencia y no un error.

Editar:

Si realmente está seguro de que su código está bien, puede ocultar la advertencia de la siguiente manera:

#pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [specifiedReceiver performSelector:specifiedSelector]; #pragma clang diagnostic pop


La advertencia debería decir así:

PerformSelector puede causar una fuga porque su selector es desconocido. ARC no sabe si el ID devuelto tiene un +1 retener recuento o no, y por lo tanto no puede gestionar correctamente la memoria del objeto devuelto.

Desafortunadamente, es solo la primera oración.

Ahora la solución:

Si recibe un valor de retorno de un método -performSelector, no puede hacer nada con respecto a la advertencia en el código, excepto ignorarlo.

NSArray *linkedNodes = [startNode performSelector:nodesArrayAccessor];

Tu mejor apuesta es esta:

#pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" NSArray *linkedNodes = [startNode performSelector:nodesArrayAccessor]; #pragma clang diagnostic pop

Lo mismo ocurre con el caso en mi pregunta inicial, donde ignoro por completo el valor de retorno. ARC debería ser lo suficientemente inteligente como para ver que no me importa la ID devuelta, y por lo tanto, el selector anónimo está casi garantizado que no será una fábrica, un constructor de conveniencia o lo que sea. Lamentablemente, ARC no lo es, por lo que se aplica la misma regla. Ignora la advertencia.

También se puede hacer para todo el proyecto configurando el indicador del compilador -Wno-arc-performSelector-leaks en "Otros indicadores de advertencia" en la configuración de compilación del proyecto.

De forma alternativa, puede eliminar la advertencia por archivo cuando agrega ese indicador en Su destino> "Fases de compilación"> "Compilar fuentes" en el lado derecho junto al archivo deseado.

Las tres soluciones son muy desagradables en mi humilde opinión así que espero que alguien encuentre una mejor.