Usando -performSelector: versus simplemente llamar al método

Respuestas:

191

Básicamente, performSelector le permite determinar dinámicamente a qué selector llamar a un selector en el objeto dado. En otras palabras, no es necesario determinar el selector antes del tiempo de ejecución.

Por lo tanto, aunque estos son equivalentes:

[anObject aMethod]; 
[anObject performSelector:@selector(aMethod)]; 

El segundo formulario le permite hacer esto:

SEL aSelector = findTheAppropriateSelectorForTheCurrentSituation();
[anObject performSelector: aSelector];

antes de enviar el mensaje.

ennuikiller
fuente
3
Vale la pena señalar que en realidad asignaría el resultado de findTheApropiadoSelectorForTheCurrentSituation () a unSelector, luego invocaría [anObject performSelector: aSelector]. @selector produce un SEL.
Daniel Yankowsky
4
Usar performSelector:es algo que probablemente solo haga si implementa target-action en su clase. Los hermanos performSelectorInBackground:withObject:ya performSelectorOnMainThread:withObject:waitUntilDone:menudo son más útiles. Para generar un hilo en segundo plano y para devolver los resultados al hilo principal desde dicho hilo en segundo plano.
PeyloW
2
performSelectortambién es útil para suprimir las advertencias de compilación. Si sabe que el método existe (como después de usarlo respondsToSelector), evitará que Xcode diga "puede que no responda your_selector". Simplemente no lo use en lugar de averiguar la causa real de la advertencia. ;)
Marc
Leí en otro hilo de StackOverflow que el uso de performSelector era un reflejo de un diseño horrible, y tenía un montón de aprobación. Ojalá pudiera encontrarlo de nuevo. Busqué en Google, restringí los resultados a stackoverflow y obtuve 18.000 resultados. Eww.
Logicsaurus Rex
"El reflejo de un diseño horrible" es demasiado simplista. Esto era lo que teníamos antes de que los bloques estuvieran disponibles, y no todos los usos son malos, ni entonces ni ahora. Aunque ahora que los bloques están disponibles, probablemente sea una mejor opción para el código nuevo, a menos que esté haciendo algo muy simple.
Ethan
16

Para este ejemplo muy básico en la pregunta,

[object doSomething];
[object performSelector:@selector(doSomething)]; 

no hay diferencia en lo que va a pasar. doSomething se ejecutará sincrónicamente por objeto. Solo "doSomething" es un método muy simple, que no devuelve nada y no requiere ningún parámetro.

si fuera algo un poco más complicado, como:

(void)doSomethingWithMyAge:(NSUInteger)age;

las cosas se complicarían, porque [object doSomethingWithMyAge: 42];

ya no se puede llamar con ninguna variante de "performSelector", porque todas las variantes con parámetros solo aceptan parámetros de objeto.

El selector aquí sería "doSomethingWithMyAge:" pero cualquier intento de

[object performSelector:@selector(doSomethingWithMyAge:) withObject:42];  

simplemente no se compilará. pasar un NSNumber: @ (42) en lugar de 42, tampoco ayudaría, porque el método espera un tipo C básico, no un objeto.

Además, existen variantes de performSelector de hasta 2 parámetros, no más. Mientras que los métodos muchas veces tienen muchos más parámetros.

Descubrí que aunque las variantes sincrónicas de performSelector:

- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

Siempre devuelve un objeto, también pude devolver un BOOL simple o NSUInteger, y funcionó.

Uno de los dos usos principales de performSelector es componer dinámicamente el nombre del método que desea ejecutar, como se explicó en una respuesta anterior. Por ejemplo

 SEL method = NSSelectorFromString([NSString stringWithFormat:@"doSomethingWithMy%@:", @"Age");
[object performSelector:method];

El otro uso es enviar de forma asincrónica un mensaje al objeto, que se ejecutará más tarde en el runloop actual. Para ello, existen otras variantes de performSelector.

- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
- (void)performSelector:(SEL)aSelector target:(id)target argument:(id)arg order:(NSUInteger)order modes:(NSArray *)modes;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg;

(sí, los reuní de varias categorías de clases de la Fundación, como NSThread, NSRunLoop y NSObject)

Cada una de las variantes tiene su propio comportamiento especial, pero todas comparten algo en común (al menos cuando waitUntilDone se establece en NO). La llamada "performSelector" volverá inmediatamente y el mensaje al objeto solo se pondrá en el runloop actual después de un tiempo.

Debido a la ejecución retrasada, naturalmente, no hay ningún valor de retorno disponible desde el método del selector, de ahí el valor de retorno - (vacío) en todas estas variantes asincrónicas.

Espero haber cubierto esto de alguna manera ...

Motti Shneor
fuente
12

@ennuikiller da en el clavo. Básicamente, los selectores generados dinámicamente son útiles para cuando no (y normalmente no es posible) sabe el nombre del método al que llamará cuando compile el código.

Una diferencia clave es que -performSelector:y los amigos (incluidas las variantes de subprocesos múltiples y retardados ) son algo limitados en el sentido de que están diseñados para su uso con métodos con parámetros 0-2. Por ejemplo, llamar -outlineView:toolTipForCell:rect:tableColumn:item:mouseLocation:con 6 parámetros y devolver el NSStringes bastante difícil de manejar y no es compatible con los métodos proporcionados.

Quinn Taylor
fuente
5
Para hacer eso, necesitarías usar un NSInvocationobjeto.
Dave DeLong
6
Otra diferencia: performSelector:todos los amigos toman argumentos de objeto, lo que significa que no puede usarlos para llamar (por ejemplo) setAlphaValue:, porque su argumento es un flotante.
Chuck
4

Los selectores son un poco como punteros de función en otros idiomas. Los usa cuando no sabe en tiempo de compilación a qué método desea llamar en tiempo de ejecución. Además, al igual que los punteros de función, solo encapsulan la parte verbal de la invocación. Si el método tiene parámetros, deberá pasarlos también.

An NSInvocationtiene un propósito similar, excepto que une más información. No solo incluye la parte del verbo, también incluye el objeto de destino y los parámetros. Esto es útil cuando desea llamar a un método en un objeto particular con parámetros particulares, no ahora sino en el futuro. Puede construir un apropiado NSInvocationy dispararlo más tarde.

Daniel Yankowsky
fuente
5
Los selectores realmente no se parecen en nada a un puntero de función en el sentido de que un puntero de función es algo que se puede llamar con argumentos y un selector se puede usar para llamar a un método particular en cualquier objeto que lo implemente; un selector no tiene el contexto completo de invocación como un puntero de función.
bbum
1
Los selectores no son lo mismo que los punteros de función, pero sigo pensando que son similares. Representan verbos. Los punteros de función C también representan verbos. Ninguno es útil sin contexto adicional. Los selectores requieren un objeto y parámetros; Los punteros de función requieren parámetros (que pueden incluir un objeto sobre el que operar). Mi punto fue resaltar en qué se diferencian de los objetos NSInvocation, que contienen todo el contexto necesario. Quizás mi comparación fue confusa, en cuyo caso me disculpo.
Daniel Yankowsky
1
Los selectores no son punteros de función. Ni siquiera cerca. En realidad, son simples cadenas de C que contienen un "nombre" de un método (en contraposición a una "función"). Ni siquiera son firmas de métodos, porque no incorporan los tipos de parámetros. Un objeto puede tener más de un método para el mismo selector (diferentes tipos de parámetros o diferentes tipos de retorno).
Motti Shneor
-7

Hay otra sutil diferencia entre los dos.

    [object doSomething]; // is executed right away

    [object performSelector:@selector(doSomething)]; // gets executed at the next runloop

Aquí está el extracto de la documentación de Apple

"performSelector: withObject: afterDelay: realiza el selector especificado en el subproceso actual durante el siguiente ciclo de bucle de ejecución y después de un período de retraso opcional. Debido a que espera hasta el siguiente ciclo de bucle de ejecución para realizar el selector, estos métodos proporcionan un mini retraso automático desde el código que se está ejecutando actualmente. Se realizan varios selectores en cola uno tras otro en el orden en que se pusieron en cola ".

avi
fuente
1
Tu respuesta es objetivamente incorrecta. La documentación que cita trata sobre performSelector:withObject:afterDelay:, pero la pregunta y su fragmento están usando performSelector:, que es un método completamente diferente. De los documentos para ello: <quote> El performSelector:método es equivalente a enviar un aSelectormensaje directamente al receptor. </quote>
jscs
3
gracias Josh por la aclaración. Estás en lo correcto; Pensé que performSelector/performSelector:withObject/performSelector:withObject:afterDelaytodos se comportaban de la misma manera, lo cual fue un error.
AVI