Usando -performSelector: em vez de apenas chamar o método

116

Ainda sou meio novo em Objective-C e me pergunto qual é a diferença entre as duas declarações a seguir.

[object performSelector:@selector(doSomething)]; 

[object doSomething];
TheGambler
fonte

Respostas:

191

Basicamente, performSelector permite que você determine dinamicamente qual seletor chamar um seletor em um determinado objeto. Em outras palavras, o seletor não precisa ser determinado antes do tempo de execução.

Portanto, mesmo que sejam equivalentes:

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

O segundo formulário permite que você faça isso:

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

antes de enviar a mensagem.

Ennuikiller
fonte
3
Vale a pena apontar que você realmente atribuiria o resultado de findTheAppropriaSelectorForTheCurrentSituation () a aSelector e, em seguida, chamaria [anObject performSelector: aSelector]. @selector produz um SEL.
Daniel Yankowsky,
4
Usar performSelector:é algo que você provavelmente só fará se implementar ação-alvo em sua classe. Os irmãos performSelectorInBackground:withObject:e performSelectorOnMainThread:withObject:waitUntilDone:muitas vezes são mais úteis. Para gerar um thread de segundo plano e para chamar de volta os resultados do thread de segundo plano para o thread principal.
PeyloW
2
performSelectortambém é útil para suprimir avisos de compilação. Se você souber que o método existe (como após o uso respondsToSelector), ele impedirá o Xcode de dizer "pode ​​não responder a your_selector". Apenas não o use em vez de descobrir a verdadeira causa do aviso. ;)
Marc
Eu li em algum outro tópico do StackOverflow que o uso de performSelector era um reflexo de um design horrível, e tinha toneladas de polegares para cima. Eu gostaria de poder encontrá-lo novamente. Eu pesquisei no google, restringindo os resultados a stackoverflow, e obtive 18.000 resultados. Eca.
Logicsaurus Rex,
"reflexo de um design horrível" é excessivamente simplista. Isso era o que tínhamos antes dos blocos estarem disponíveis, e nem todos os usos são ruins, então ou agora. Embora agora os blocos estejam disponíveis, essa é provavelmente a melhor escolha para o novo código, a menos que você esteja fazendo algo muito simples.
Ethan
16

Para este exemplo básico na questão,

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

não há diferença no que vai acontecer. doSomething será executado de forma síncrona pelo objeto. Apenas "doSomething" é um método muito simples, que não retorna nada e não requer nenhum parâmetro.

fosse algo um pouco mais complicado, como:

(void)doSomethingWithMyAge:(NSUInteger)age;

as coisas ficariam complicadas, porque [object doSomethingWithMyAge: 42];

não pode mais ser chamado com qualquer variante de "performSelector", porque todas as variantes com parâmetros aceitam apenas parâmetros de objeto.

O seletor aqui seria "doSomethingWithMyAge:" mas qualquer tentativa de

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

simplesmente não compila. passar um NSNumber: @ (42) em vez de 42 também não ajudaria, porque o método espera um tipo C básico - não um objeto.

Além disso, existem variantes performSelector de até 2 parâmetros, não mais. Embora os métodos muitas vezes tenham muitos mais parâmetros.

Eu descobri que, embora sejam variantes síncronas de performSelector:

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

sempre retornava um objeto, consegui retornar um simples BOOL ou NSUInteger também, e funcionou.

Um dos dois principais usos de performSelector é compor dinamicamente o nome do método que você deseja executar, conforme explicado em uma resposta anterior. Por exemplo

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

O outro uso é enviar assincronamente uma mensagem para o objeto, que será executado posteriormente no runloop atual. Para isso, existem várias outras variantes do 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;

(sim, reuni-os de várias categorias de classe Foundation, como NSThread, NSRunLoop e NSObject)

Cada uma das variantes tem seu próprio comportamento especial, mas todas compartilham algo em comum (pelo menos quando waitUntilDone está definido como NO). A chamada "performSelector" retornaria imediatamente e a mensagem para o objeto só seria colocada no runloop atual após algum tempo.

Por causa da execução atrasada - naturalmente, nenhum valor de retorno está disponível do método do seletor, portanto, o valor de retorno - (vazio) em todas essas variantes assíncronas.

Espero ter coberto isso de alguma forma ...

Motti Shneor
fonte
12

@ennuikiller está certo. Basicamente, os seletores gerados dinamicamente são úteis quando você não sabe (e geralmente não pode) saber o nome do método que estará chamando ao compilar o código.

Uma diferença importante é que -performSelector:e amigos (incluindo as variantes multi-threaded e atrasadas ) são um tanto limitados, pois são projetados para uso com métodos com parâmetros 0-2. Por exemplo, chamar -outlineView:toolTipForCell:rect:tableColumn:item:mouseLocation:com 6 parâmetros e retornar o NSStringé muito complicado e não é compatível com os métodos fornecidos.

Quinn Taylor
fonte
5
Para fazer isso, você precisa usar um NSInvocationobjeto.
Dave DeLong
6
Outra diferença: performSelector:e todos os amigos aceitam argumentos de objeto, o que significa que você não pode usá-los para chamar (por exemplo) setAlphaValue:, porque seu argumento é um float.
Chuck
4

Os seletores são um pouco como os ponteiros de função em outras linguagens. Você os usa quando não sabe em tempo de compilação qual método deseja chamar em tempo de execução. Além disso, como os ponteiros de função, eles encapsulam apenas a parte do verbo da invocação. Se o método tiver parâmetros, você também precisará passá-los.

Um NSInvocationserve a um propósito semelhante, exceto pelo fato de reunir mais informações. Não inclui apenas a parte do verbo, também inclui o objeto de destino e os parâmetros. Isso é útil quando você deseja chamar um método em um objeto específico com parâmetros específicos, não agora, mas no futuro. Você pode construir um apropriado NSInvocatione dispará-lo mais tarde.

Daniel Yankowsky
fonte
5
Os seletores realmente não são como um ponteiro de função em que um ponteiro de função é algo que você pode chamar com argumentos e um seletor pode ser usado para chamar um método específico em qualquer objeto que o implemente; um seletor não tem o contexto completo de invocação como um ponteiro de função.
bbum
1
Os seletores não são iguais aos ponteiros de função, mas ainda acho que são semelhantes. Eles representam verbos. Os ponteiros de função C também representam verbos. Nenhum dos dois é útil sem contexto adicional. Os seletores requerem um objeto e parâmetros; ponteiros de função requerem parâmetros (que podem incluir um objeto sobre o qual operar). Meu ponto era destacar como eles são diferentes dos objetos NSInvocation, que contêm todo o contexto necessário. Talvez minha comparação tenha sido confusa, caso em que peço desculpas.
Daniel Yankowsky,
1
Seletores não são ponteiros de função. Nem mesmo perto. Eles são strings C simples na realidade, que contêm um "nome" de um método (em oposição a 'função'). Eles nem mesmo são assinaturas de método, porque não incorporam os tipos de parâmetros. Um objeto pode ter mais de um método para o mesmo seletor (diferentes tipos de parâmetros ou diferentes tipos de retorno).
Motti Shneor
-7

Existe outra diferença sutil entre os dois.

    [object doSomething]; // is executed right away

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

Aqui está o trecho da documentação da Apple

"performSelector: withObject: afterDelay: Executa o seletor especificado no encadeamento atual durante o próximo ciclo de loop de execução e após um período de atraso opcional. Como ele espera até o próximo ciclo de loop de execução para realizar o seletor, esses métodos fornecem um mini atraso automático de o código em execução no momento. Vários seletores na fila são executados um após o outro na ordem em que foram colocados na fila. "

avi
fonte
1
Sua resposta é factualmente incorreta. A documentação que você cita é sobre performSelector:withObject:afterDelay:, mas a pergunta e seu trecho estão usando performSelector:, que é um método totalmente diferente. Dos documentos para ele: <quote> O performSelector:método é equivalente a enviar uma aSelectormensagem diretamente para o destinatário. </quote>
jscs
3
obrigado Josh pelo esclarecimento. Você está certo; Achei que performSelector/performSelector:withObject/performSelector:withObject:afterDelaytodos se comportavam da mesma maneira, o que foi um erro.
avi