performSelector puede causar una fuga porque su selector es desconocido

1258

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?

Eduardo Scoz
fuente
3
El nombre de la variable es dinámico, depende de muchas otras cosas. Existe el riesgo de que llame a algo que no existe, pero ese no es el problema.
Eduardo Scoz
66
@matt ¿por qué llamar a un método dinámicamente en un objeto sería una mala práctica? ¿No es todo el propósito de NSSelectorFromString () apoyar esta práctica?
Eduardo Scoz
77
También debería / podría probar [_controller respondeToSelector: mySelector] antes de configurarlo mediante performSelector:
mattacular
50
@mattacular Ojalá pudiera votar: "Eso ... es una mala práctica".
ctpenrose
66
Si sabe que la cadena es literal, simplemente use @selector () para que el compilador pueda saber cuál es el nombre del selector. Si su código real está llamando a NSSelectorFromString () con una cadena construida o provista en tiempo de ejecución, entonces debe usar NSSelectorFromString ().
Chris Page

Respuestas:

1212

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:

if (!_controller) { return; }
SEL selector = NSSelectorFromString(@"someMethod");
IMP imp = [_controller methodForSelector:selector];
void (*func)(id, SEL) = (void *)imp;
func(_controller, selector);

O más brevemente (aunque difícil de leer y sin el protector):

SEL selector = NSSelectorFromString(@"someMethod");
((void (*)(id, SEL))[_controller methodForSelector:selector])(_controller, selector);

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 NSObjectresponden methodForSelector:, pero también se puede usar class_getMethodImplementationen el tiempo de ejecución de Objective-C (útil si solo tiene una referencia de protocolo, como id<SomeProto>). Estos punteros de función se denominan IMPs, y son typedefpunteros 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ícitos selfy _cmdde 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:

SEL selector = NSSelectorFromString(@"processRegion:ofView:");
IMP imp = [_controller methodForSelector:selector];
CGRect (*func)(id, SEL, CGRect, UIView *) = (void *)imp;
CGRect result = _controller ?
  func(_controller, selector, someRect, someView) : CGRectZero;

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

En realidad, solo hay 4 cosas que ARC consideraría para el valor de retorno: 4

  1. Tipos Ignorar no-objeto ( void, int, etc)
  2. Retenga el valor del objeto, luego suéltelo cuando ya no se use (suposición estándar)
  3. Libere nuevos valores de objetos cuando ya no se usen (métodos en init/ copyfamily o atribuidos con ns_returns_retained)
  4. No hacer nada y asumir que el valor del objeto devuelto será válido en el ámbito local (hasta que se agote el conjunto de versiones más interno, atribuido con 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 voidu 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 devuelve methodForSelector: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á llamando someMethoddevuelve un objeto no incluido (incluido void), 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 la IMPmetodologí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:

[_controller performSelector:@selector(someMethod)];

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 NSMethodInvocationpara 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:

  1. Las primeras versiones de Objective-C permiten performSelector:(gestión de memoria manual)
  2. Objective-C con ARC advierte sobre el uso de performSelector:
  3. Swift no tiene acceso 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, selfy _cmdque se añaden de forma implícita cuando se llama a un método.

2 Llamar a una NULLfunció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 una IMPde methodForSelector:(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 idy 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.

wbyoung
fuente
@wbyoung Si su código resuelve el problema de retención, me pregunto por qué los performSelector:métodos no se implementan de esta manera. Tienen una firma de método estricta (devolución id, toma uno o dos ids), por lo que no es necesario manejar tipos primitivos.
Tricertops
1
@Andy, el argumento se maneja en función de la definición del prototipo del método (no será retenido / liberado). La preocupación se basa principalmente en el tipo de devolución.
wbyoung
2
El "ejemplo complejo" da un error 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!
Stan James
2
void (*func)(id, SEL) = (void *)imp;no se compila, lo he reemplazado convoid (*func)(id, SEL) = (void (*)(id, SEL))imp;
Davyd Geyl
1
cambiar void (*func)(id, SEL) = (void *)imp;a <…> = (void (*))imp;o<…> = (void (*) (id, SEL))imp;
Isaak Osipovich Dunayevsky
1182

En el compilador LLVM 3.0 en Xcode 4.2 puede suprimir la advertencia de la siguiente manera:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    [self.ticketTarget performSelector: self.ticketAction withObject: self];
#pragma clang diagnostic pop

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:

#define SuppressPerformSelectorLeakWarning(Stuff) \
    do { \
        _Pragma("clang diagnostic push") \
        _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
        Stuff; \
        _Pragma("clang diagnostic pop") \
    } while (0)

Puede usar la macro de esta manera:

SuppressPerformSelectorLeakWarning(
    [_target performSelector:_action withObject:self]
);

Si necesita el resultado del mensaje realizado, puede hacer esto:

id result;
SuppressPerformSelectorLeakWarning(
    result = [_target performSelector:_action withObject:self]
);
Scott Thompson
fuente
Este método puede causar pérdidas de memoria cuando la optimización se establece en algo distinto de Ninguno.
Eric
44
@Eric No, no puede, a menos que invoques métodos divertidos como "initSomething" o "newSomething" o "somethingCopy".
Andrey Tarantsov
3
@Julian Eso funciona, pero apaga la advertencia para todo el archivo; es posible que no lo necesite o lo desee. Envolverlo con las popy push-pragmas es mucho más limpio y seguro.
Emil
2
Todo lo que hace es silenciar el compilador. Esto no resuelve el problema. Si el selector no existe, estás bastante jodido.
Andra Todorescu
2
Esto debe usarse solo cuando está envuelto por una if ([_target respondsToSelector:_selector]) {lógica o similar.
208

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

sergio
fuente
55
Gracias por la respuesta, investigaré más sobre esto para ver qué está pasando. ¿Alguna idea de cómo puedo evitar la advertencia y hacer que desaparezca? Odiaría tener la advertencia en mi código para siempre para lo que es una llamada segura.
Eduardo Scoz
84
Así que alguien de Apple me confirmó en sus foros que este es el caso. Agregarán una anulación olvidada para permitir que las personas deshabiliten esta advertencia en futuras versiones. Gracias.
Eduardo Scoz
55
Esta respuesta plantea algunas preguntas, como si ARC intenta hacer determinaciones sobre cuándo publicar algo basado en nombres de convenciones y métodos, entonces, ¿cómo es el "conteo de referencias"? El comportamiento que describe suena solo marginalmente mejor que completamente arbitrario, si ARC asume que el código sigue una determinada convención en lugar de realizar un seguimiento de las referencias, sin importar qué convención se siga.
Aroth
8
ARC automatiza el proceso de agregar retenciones y lanzamientos en la compilación. No es recolección de basura (es por eso que es tan increíblemente rápido y de bajo costo). No es arbitrario en absoluto. Las reglas predeterminadas se basan en convenciones de ObjC bien establecidas que se han aplicado constantemente durante décadas. Esto evita la necesidad de agregar explícitamente un __attributemé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).
Rob Napier
8
¿Entonces ya no podemos tener un ivar de tipo SELy asignar diferentes selectores dependiendo de la situación? Así se hace, lenguaje dinámico ...
Nicolas Miari
121

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.

0xced
fuente
12
Tenga en cuenta que puede agregar el mismo indicador para archivos específicos en lugar de todo el proyecto. Si mira en Fases de compilación-> Compilar orígenes, puede establecer indicadores de compilador por archivo (tal como desea hacer para excluir archivos de ARC). En mi proyecto, solo un archivo debe usar selectores de esta manera, así que lo excluí y dejé los otros.
Michael
111

Como solución alternativa hasta que el compilador permita anular la advertencia, puede usar el tiempo de ejecución

objc_msgSend(_controller, NSSelectorFromString(@"someMethod"));

en vez de

[_controller performSelector:NSSelectorFromString(@"someMethod")];

Tendrás que

#import <objc/message.h>

jluckyiv
fuente
8
ARC reconoce las convenciones de Cocoa y luego agrega retenciones y liberaciones basadas en esas convenciones. Debido a que C no sigue esas convenciones, ARC lo obliga a usar técnicas manuales de administración de memoria. Si crea un objeto CF, debe CFRelease (). Si dispatch_queue_create (), debe dispatch_release (). En pocas palabras, si desea evitar las advertencias ARC, puede evitarlas mediante el uso de objetos C y la gestión manual de la memoria. Además, puede deshabilitar ARC por archivo utilizando el indicador del compilador -fno-objc-arc en ese archivo.
jluckyiv
8
No sin lanzar, no puedes. Varargs no es lo mismo que una lista de argumentos explícitamente escrita. Generalmente funcionará por coincidencia, pero no considero que "por coincidencia" sea correcto.
bbum
21
¡No hagas eso, [_controller performSelector:NSSelectorFromString(@"someMethod")];y objc_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.
0xced
55
@ 0xced En este caso, está bien. objc_msgSend no creará una discrepancia de firma de método para ningún selector que hubiera funcionado correctamente en performSelector: o sus variantes, ya que solo toman objetos como parámetros. Siempre que todos sus parámetros sean punteros (incluidos los objetos), dobles y NSInteger / long, y su tipo de retorno sea nulo, puntero o largo, objc_msgSend funcionará correctamente.
Matt Gallagher
88

Para ignorar el error solo en el archivo con el selector de ejecución, agregue un #pragma de la siguiente manera:

#pragma clang diagnostic ignored "-Warc-performSelector-leaks"

Esto ignoraría la advertencia en esta línea, pero aún lo permitiría durante el resto de su proyecto.

Barlow Tucker
fuente
66
Supongo que también puede volver a activar la advertencia inmediatamente después del método en cuestión #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.
Rob
2
También puede restaurar su estado de configuración del compilador anterior utilizando #pragma clang diagnostic warning pushantes de realizar cualquier cambio y #pragma clang diagnostic warning poprestaurar el estado anterior. Útil si está desactivando cargas y no desea tener muchas líneas de pragma de reactivación en su código.
deanWombourne
¿Solo ignorará la siguiente línea?
hfossli
70

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:

[_controller performSelector:NSSelectorFromString(@"someMethod")
    withObject:nil
    afterDelay:0];

Esto elimina la advertencia, presumiblemente porque asegura al compilador que ningún objeto puede ser devuelto y de alguna manera mal administrado.

mate
fuente
2
¿Sabes si esto realmente resuelve los problemas relacionados con la administración de la memoria, o si tiene los mismos problemas pero Xcode no es lo suficientemente inteligente como para advertirte con este código?
Aaron Brager
¡Esto semánticamente no es lo mismo! Usando performSelector: withObject: AfterDelay: realizará el selector en la próxima ejecución del runloop. Por lo tanto, este método vuelve inmediatamente.
Florian
10
@Florian ¡Por supuesto que no es lo mismo! Lea mi respuesta: digo si es aceptable, porque el resultado es nulo y los ciclos de ejecución. Eso es primera oración de mi respuesta.
mate
34

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.

#define SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING(code)                        \
    _Pragma("clang diagnostic push")                                        \
    _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"")     \
    code;                                                                   \
    _Pragma("clang diagnostic pop")                                         \


SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING(
    return [_target performSelector:_action withObject:self]
);
syvex
fuente
66
returnno 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.
Uasi
31

Este código no involucra indicadores del compilador o llamadas directas en tiempo de ejecución:

SEL selector = @selector(zeroArgumentMethod);
NSMethodSignature *methodSig = [[self class] instanceMethodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setSelector:selector];
[invocation setTarget:self];
[invocation invoke];

NSInvocationpermite establecer múltiples argumentos, de modo que, a diferencia de performSelectoresto, funcionará en cualquier método.

Benedict Cohen
fuente
3
¿Sabes si esto realmente resuelve los problemas relacionados con la administración de la memoria, o si tiene los mismos problemas pero Xcode no es lo suficientemente inteligente como para advertirte con este código?
Aaron Brager
1
Se podría decir que resuelve los problemas de administración de memoria; pero esto se debe a que básicamente le permite especificar el comportamiento. Por ejemplo, puede optar por dejar que la invocación retenga los argumentos o no. Hasta donde yo sé, intenta solucionar los problemas de desajuste de firma que pueden aparecer confiando en que usted sabe lo que está haciendo y no le proporciona datos incorrectos. No estoy seguro de si todas las comprobaciones se pueden realizar en tiempo de ejecución. Como se menciona en otro comentario, mikeash.com/pyblog/... explica muy bien lo que pueden hacer los desajustes.
Mihai Timar
20

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.

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "Debug.h" // not given; just an assert

@interface NSObject (Extras)

// Enforce the rule that the selector used must return void.
- (void) performVoidReturnSelector:(SEL)aSelector withObject:(id)object;
- (void) performVoidReturnSelector:(SEL)aSelector;

@end

@implementation NSObject (Extras)

// Apparently the reason the regular performSelect gives a compile time warning is that the system doesn't know the return type. I'm going to (a) make sure that the return type is void, and (b) disable this warning
// See http://stackoverflow.com/questions/7017281/performselector-may-cause-a-leak-because-its-selector-is-unknown

- (void) checkSelector:(SEL)aSelector {
    // See http://stackoverflow.com/questions/14602854/objective-c-is-there-a-way-to-check-a-selector-return-value
    Method m = class_getInstanceMethod([self class], aSelector);
    char type[128];
    method_getReturnType(m, type, sizeof(type));

    NSString *message = [[NSString alloc] initWithFormat:@"NSObject+Extras.performVoidReturnSelector: %@.%@ selector (type: %s)", [self class], NSStringFromSelector(aSelector), type];
    NSLog(@"%@", message);

    if (type[0] != 'v') {
        message = [[NSString alloc] initWithFormat:@"%@ was not void", message];
        [Debug assertTrue:FALSE withMessage:message];
    }
}

- (void) performVoidReturnSelector:(SEL)aSelector withObject:(id)object {
    [self checkSelector:aSelector];

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    // Since the selector (aSelector) is returning void, it doesn't make sense to try to obtain the return result of performSelector. In fact, if we do, it crashes the app.
    [self performSelector: aSelector withObject: object];
#pragma clang diagnostic pop    
}

- (void) performVoidReturnSelector:(SEL)aSelector {
    [self checkSelector:aSelector];

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    [self performSelector: aSelector];
#pragma clang diagnostic pop
}

@end
Chris Prince
fuente
¿Debería 'v' ser reemplazado por _C_VOID? _C_VOID se declara en <objc / runtime.h>.
Rik Renich el
16

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 directo performSelectorque he usado algunas veces:

[NSApp sendAction: NSSelectorFromString(@"someMethod") to: _controller from: nil];

Estos parecen ser un reemplazo limpio, seguro para ARC y casi idéntico performSelectorsin tener que preocuparse demasiado objc_msgSend().

Sin embargo, no tengo idea si hay un análogo disponible en iOS.

Patrick Perini
fuente
66
Gracias por la inclusión de este .. Está disponible en iOS: [[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!
Eduardo Scoz
2
Ew! Tendrá más sobrecarga (ya que necesita verificar si el método está disponible y subir la cadena de respuesta si no lo está) y tener un comportamiento de error diferente (subir la cadena de respuesta y devolver NO si no puede encontrar nada que responde al método, en lugar de simplemente estrellarse). Tampoco funciona cuando quieres el idfrom-performSelector:...
tc.
2
@tc. No "sube por la cadena de respuesta" a menos que 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. :)
mate
15

La respuesta de Matt Galloway en este hilo explica el por qué:

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 recuento de retención de 1, pero el segundo devuelve un objeto que se lanza automáticamente?

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

c roald
fuente
14

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

@interface Dummy : NSObject <NSCopying>
@end

@implementation Dummy

- (id)copyWithZone:(NSZone *)zone {
  return [[Dummy alloc] init];
}

- (id)clone {
  return [[Dummy alloc] init];
}

@end

void CopyDummy(Dummy *dummy) {
  __unused Dummy *dummyClone = [dummy copy];
}

void CloneDummy(Dummy *dummy) {
  __unused Dummy *dummyClone = [dummy clone];
}

void CopyDummyWithLeak(Dummy *dummy, SEL copySelector) {
  __unused Dummy *dummyClone = [dummy performSelector:copySelector];
}

void CloneDummyWithoutLeak(Dummy *dummy, SEL cloneSelector) {
  __unused Dummy *dummyClone = [dummy performSelector:cloneSelector];
}

int main(int argc, const char * argv[]) {
  @autoreleasepool {
    Dummy *dummy = [[Dummy alloc] init];
    for (;;) { @autoreleasepool {
      //CopyDummy(dummy);
      //CloneDummy(dummy);
      //CloneDummyWithoutLeak(dummy, @selector(clone));
      CopyDummyWithLeak(dummy, @selector(copy));
      [NSThread sleepForTimeInterval:1];
    }} 
  }
  return 0;
}

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: ingrese la descripción de la imagen aquí ... y no hay pérdidas de memoria en ningún otro caso: ingrese la descripción de la imagen aquí

Pavel Osipov
fuente
6

Para hacer que la macro de Scott Thompson sea más genérica:

// String expander
#define MY_STRX(X) #X
#define MY_STR(X) MY_STRX(X)

#define MYSilenceWarning(FLAG, MACRO) \
_Pragma("clang diagnostic push") \
_Pragma(MY_STR(clang diagnostic ignored MY_STR(FLAG))) \
MACRO \
_Pragma("clang diagnostic pop")

Entonces úsalo así:

MYSilenceWarning(-Warc-performSelector-leaks,
[_target performSelector:_action withObject:self];
                )
Ben Flynn
fuente
FWIW, no agregué la macro. Alguien agregó eso a mi respuesta. Personalmente, no usaría la macro. El pragma está ahí para solucionar un caso especial en el código y los pragmas son muy explícitos y directos sobre lo que está sucediendo. Prefiero mantenerlos en su lugar en lugar de esconderlos o abstraerlos detrás de una macro, pero solo soy yo. YMMV.
Scott Thompson
@ScottThompson Eso es justo. Para mí es fácil buscar esta macro en mi base de código y generalmente también agrego una advertencia no silenciada para tratar el problema subyacente.
Ben Flynn
6

¡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 parampuede ser así nilsi así lo desea:

Ruta segura, mismo comportamiento conceptual:

// GREAT
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:YES];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:YES modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:YES];
[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:YES modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

Ruta segura, comportamiento ligeramente diferente:

(Ver esta respuesta)
Use cualquier hilo en lugar de [NSThread mainThread].

// GOOD
[_controller performSelector:selector withObject:anArgument afterDelay:0];
[_controller performSelector:selector withObject:anArgument afterDelay:0 inModes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

[_controller performSelectorInBackground:selector withObject:anArgument];

[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:NO];
[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:NO modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

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 .

// AT YOUR OWN RISK
[_controller performSelector:selector];
[_controller performSelector:selector withObject:anArgument];
[_controller performSelector:selector withObject:anArgument withObject:nil];
Arquitecto Swift
fuente
3
La redacción es muy incorrecta. Las rutas seguras no son más seguras que peligrosas. Podría decirse que es más peligroso porque oculta la advertencia implícitamente.
Bryan Chen
Arreglaré la redacción para que no sea insultante, pero mantengo mi palabra. La única vez que encuentro aceptable la advertencia de silenciamiento es si no soy el propietario del código. Ningún ingeniero puede mantener con seguridad el código silenciado sin comprender todas las consecuencias, lo que significaría leer este argumento, y esta práctica es simplemente arriesgada; especialmente si considera las 12 alternativas sólidas en inglés simple.
SwiftArchitect
1
No. No entendiste mi punto. Usar noperformSelectorOnMainThread 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
Bryan Chen el
Es cierto que realizar un selector en un - (void)método no es el verdadero problema.
SwiftArchitect
¿Y cómo llamar a un selector con múltiples argumentos a través de esto y estar seguro al mismo tiempo? @SwiftArchitect
Catalin
4

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.

honus
fuente
En realidad, los bloques hacen que sea muy fácil crear accidentalmente un ciclo de retención que ARC no resuelve. Todavía deseo que haya una advertencia del compilador cuando implícitamente lo selfusaste a través de un ivar (por ejemplo, en ivarlugar de self->ivar).
tc.
¿Te refieres a -Wimplicit-reten-self?
OrangeDog
2

En lugar de usar el enfoque de bloque, lo que me dio algunos problemas:

    IMP imp = [_controller methodForSelector:selector];
    void (*func)(id, SEL) = (void *)imp;

Usaré NSInvocation, así:

    -(void) sendSelectorToDelegate:(SEL) selector withSender:(UIButton *)button 

    if ([delegate respondsToSelector:selector])
    {
    NSMethodSignature * methodSignature = [[delegate class]
                                    instanceMethodSignatureForSelector:selector];
    NSInvocation * delegateInvocation = [NSInvocation
                                   invocationWithMethodSignature:methodSignature];


    [delegateInvocation setSelector:selector];
    [delegateInvocation setTarget:delegate];

    // remember the first two parameter are cmd and self
    [delegateInvocation setArgument:&button atIndex:2];
    [delegateInvocation invoke];
    }
supersabbath
fuente
1

Si no necesita pasar ningún argumento, debe usar una solución fácil valueForKeyPath. Esto incluso es posible en un Classobjeto.

NSString *colorName = @"brightPinkColor";
id uicolor = [UIColor class];
if ([uicolor respondsToSelector:NSSelectorFromString(colorName)]){
    UIColor *brightPink = [uicolor valueForKeyPath:colorName];
    ...
}
arsenio
fuente
-2

También podría usar un protocolo aquí. Entonces, cree un protocolo como este:

@protocol MyProtocol
-(void)doSomethingWithObject:(id)object;
@end

En su clase que necesita llamar a su selector, entonces tiene una propiedad @.

@interface MyObject
    @property (strong) id<MyProtocol> source;
@end

Cuando necesite llamar @selector(doSomethingWithObject:)a una instancia de MyObject, haga esto:

[self.source doSomethingWithObject:object];
Damon
fuente
2
Hola Wu, gracias, pero el punto de usar NSSelectorFromString es cuando no sabes a qué selector quieres llamar durante el tiempo de ejecución.
Eduardo Scoz