Como usar performSelector: withObject: afterDelay: com tipos primitivos em Cocoa?

O método NSObject performSelector:withObject:afterDelay: permite-me invocar um método no object com um argumento de object após um certo tempo. Ele não pode ser usado para methods com um argumento que não seja object (por exemplo, ints, floats, structs, pointers que não sejam object, etc.).

Qual é a maneira mais simples de obter a mesma coisa com um método com um argumento que não seja object? Eu sei que para performSelector:withObject: regular performSelector:withObject: a solução é usar NSInvocation (que por sinal é realmente complicado). Mas eu não sei como lidar com a parte “delay”.

Obrigado,

Aqui está o que eu costumava chamar algo que eu não poderia mudar usando NSInvocation:

 SEL theSelector = NSSelectorFromString(@"setOrientation:animated:"); NSInvocation *anInvocation = [NSInvocation invocationWithMethodSignature: [MPMoviePlayerController instanceMethodSignatureForSelector:theSelector]]; [anInvocation setSelector:theSelector]; [anInvocation setTarget:theMovie]; UIInterfaceOrientation val = UIInterfaceOrientationPortrait; BOOL anim = NO; [anInvocation setArgument:&val atIndex:2]; [anInvocation setArgument:&anim atIndex:3]; [anInvocation performSelector:@selector(invoke) withObject:nil afterDelay:1]; 

Apenas envolva o float, boolean, int ou similar em um NSNumber.

Para estruturas, não conheço uma solução útil, mas você poderia criar uma class ObjC separada que possua essa estrutura.

NÃO USE ESSA RESPOSTA. Eu só o deixei para fins históricos. VEJA OS COMENTÁRIOS ABAIXO.

Existe um truque simples se é um parâmetro BOOL.

Passe nulo por NÃO e auto por SIM. nil é convertido para o valor BOOL de NO. self é convertido para o valor BOOL de YES.

Essa abordagem é dividida se for algo diferente de um parâmetro BOOL.

Assumindo que o self é um UIView.

 //nil will be cast to NO when the selector is performed [self performSelector:@selector(setHidden:) withObject:nil afterDelay:5.0]; //self will be cast to YES when the selector is performed [self performSelector:@selector(setHidden:) withObject:self afterDelay:10.0]; 

Talvez NSValue , apenas certifique-se de que seus pointers ainda sejam válidos após o atraso (ou seja, nenhum object alocado na pilha).

Eu sei que esta é uma pergunta antiga, mas se você estiver criando o iOS SDK 4+, poderá usar blocos para fazer isso com muito pouco esforço e torná-lo mais legível:

 double delayInSeconds = 2.0; int primitiveValue = 500; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ [self doSomethingWithPrimitive:primitiveValue]; }); 

Blocos são o caminho a percorrer. Você pode ter parâmetros complexos, digitar segurança, e é muito mais simples e seguro do que a maioria das respostas antigas aqui. Por exemplo, você poderia simplesmente escrever:

 [MONBlock performBlock:^{[obj setFrame:SOMETHING];} afterDelay:2]; 

Os blocos permitem capturar listas de parâmetros arbitrários, objects de referência e variables.

Implementação de apoio (básico):

 @interface MONBlock : NSObject + (void)performBlock:(void(^)())pBlock afterDelay:(NSTimeInterval)pDelay; @end @implementation MONBlock + (void)imp_performBlock:(void(^)())pBlock { pBlock(); } + (void)performBlock:(void(^)())pBlock afterDelay:(NSTimeInterval)pDelay { [self performSelector:@selector(imp_performBlock:) withObject:[pBlock copy] afterDelay:pDelay]; } @end 

Exemplo:

 int main(int argc, const char * argv[]) { @autoreleasepool { __block bool didPrint = false; int pi = 3; // close enough =p [MONBlock performBlock:^{NSLog(@"Hello, World! pi is %i", pi); didPrint = true;} afterDelay:2]; while (!didPrint) { [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeInterval:0.1 sinceDate:NSDate.date]]; } NSLog(@"(Bye, World!)"); } return 0; } 

Veja também a resposta de Michael (+1) para outro exemplo.

PerformSelector: WithObject sempre pega um object, então para passar argumentos como int / double / float etc ….. Você pode usar algo assim.

 //NSNumber is an object.. [self performSelector:@selector(setUserAlphaNumber:) withObject: [NSNumber numberWithFloat: 1.0f] afterDelay:1.5]; -(void) setUserAlphaNumber: (NSNumber*) number{ [txtUsername setAlpha: [number floatValue] ]; } 

Da mesma forma que você pode usar [NSNumber numberWithInt:] etc …. e no método de recebimento você pode converter o número em seu formato como [number int] ou [number double].

Eu sempre recomendo que você use NSMutableArray como o object a ser transmitido. Isso porque você pode passar vários objects, como o botão pressionado e outros valores. NSNumber, NSInteger e NSString são apenas contêineres de algum valor. Certifique-se de que quando você obtiver o object da matriz que você se refere a um tipo de contêiner correto. Você precisa passar em contêineres NS. Lá você pode testar o valor. Lembre-se de que os contêineres usam isEqual quando os valores são comparados.

 #define DELAY_TIME 5 -(void)changePlayerGameOnes:(UIButton*)sender{ NSNumber *nextPlayer = [NSNumber numberWithInt:[gdata.currentPlayer intValue]+1 ]; NSMutableArray *array = [[NSMutableArray alloc]initWithObjects:sender, nil]; [array addObject:nextPlayer]; [self performSelector:@selector(next:) withObject:array afterDelay:DELAY_TIME]; } -(void)next:(NSMutableArray*)nextPlayer{ if(gdata != nil){ //if game choose next player [self nextPlayer:[nextPlayer objectAtIndex:1] button:[nextPlayer objectAtIndex:0]]; } } 

Eu também queria fazer isso, mas com um método que recebe um parâmetro BOOL. Embrulhando o valor bool com NSNumber, FALHOU PARA PASSAR O VALOR. Eu não tenho ideia do porquê.

Então acabei fazendo um simples hack. Eu coloquei o parâmetro requerido em outra function dummy e chamei essa function usando o performSelector, onde withObject = nil;

 [self performSelector:@selector(dummyCaller:) withObject:nil afterDelay:5.0]; -(void)dummyCaller { [self myFunction:YES]; } 

Eu acho que a maneira mais rápida (mas um tanto suja) de fazer isso é invocar objc_msgSend diretamente. No entanto, é perigoso invocá-lo diretamente porque você precisa ler a documentação e certificar-se de estar usando a variante correta para o tipo de valor de retorno e porque objc_msgSend é definido como vararg para conveniência do compilador, mas na verdade é implementado como cola rápida de assembly . Aqui está algum código usado para chamar um método delegado – [delegate integerDidChange:] que usa um único argumento inteiro.

 #import  SEL theSelector = @selector(integerDidChange:); if ([self.delegate respondsToSelector:theSelector]) { typedef void (*IntegerDidChangeFuncPtrType)(id, SEL, NSInteger); IntegerDidChangeFuncPtrType MyFunction = (IntegerDidChangeFuncPtrType)objc_msgSend; MyFunction(self.delegate, theSelector, theIntegerThatChanged); } 

Isso primeiro salva o seletor, já que vamos nos referir a ele várias vezes e seria fácil criar um erro de digitação. Em seguida, ele verifica se o delegado realmente responde ao seletor – pode ser um protocolo opcional. Em seguida, ele cria um tipo de ponteiro de function que especifica a assinatura real do seletor. Lembre-se de que todas as mensagens do Objective-C têm dois primeiros argumentos ocultos, o object sendo enviado e o seletor sendo enviado. Em seguida, criamos um ponteiro de function do tipo apropriado e o definimos para apontar para a function objc_msgSend subjacente. Tenha em mente que, se o valor de retorno for float ou struct, você precisará usar uma variante diferente de objc_msgSend. Finalmente, envie a mensagem usando o mesmo maquinário que o Objective-C usa sob as folhas.

Você poderia apenas usar o NSTimer para chamar um seletor:

 [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(yourMethod:) userInfo:nil repeats:NO] 

Chamar performSelector com um NSNumber ou outro NSValue não funcionará. Em vez de usar o valor do NSValue / NSNumber, ele irá efetivamente converter o ponteiro para um int, float ou qualquer outro e usar isso.

Mas a solução é simples e óbvia. Crie o NSInvocation e chame

[invocation performSelector:@selector(invoke) withObject:nil afterDelay:delay]

Pehaps … ok, muito provavelmente, estou faltando alguma coisa, mas por que não criar um tipo de object, digamos NSNumber, como um contêiner para sua variável de tipo não object, como CGFloat?

 CGFloat myFloat = 2.0; NSNumber *myNumber = [NSNumber numberWithFloat:myFloat]; [self performSelector:@selector(MyCalculatorMethod:) withObject:myNumber afterDelay:5.0];