web-dev-qa-db-fra.com

Comment utiliser performSelector: withObject: afterDelay: with primitive types in Cocoa?

La méthode NSObjectperformSelector:withObject:afterDelay: me permet d’appeler une méthode sur l’objet avec un argument d’objet après un certain temps. Il ne peut pas être utilisé pour les méthodes avec un argument non-objet (par exemple, ints, floats, structs, pointeurs non-objet, etc.).

Quel est le moyen le plus simple de réaliser la même chose avec une méthode avec un argument non-objet? Je sais que pour les habitués performSelector:withObject:, la solution consiste à utiliser NSInvocation (ce qui est d’ailleurs très compliqué). Mais je ne sais pas comment gérer la partie "délai".

Merci,

96
user102008

Voici ce que j’appelais quelque chose que je ne pouvais pas changer avec 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];
69
Morty

Il suffit d’envelopper le float, boolean, int ou similaire dans un NSNumber.

Pour les structures, je ne connais pas de solution pratique, mais vous pouvez créer une classe ObjC distincte possédant une telle structure.

35
harms

NE PAS UTILISER CETTE RÉPONSE. Je ne l'ai laissé que pour des raisons historiques. VOIR LES COMMENTAIRES CI-DESSOUS.

Il y a une astuce simple s'il s'agit d'un paramètre BOOL.

Passez nil pour NON et moi pour OUI. nil est renvoyé à la valeur BOOL de NO. self est lancé à la valeur BOOL de YES.

Cette approche échoue s'il s'agit d'un paramètre autre que BOOL.

En supposant que soi est un 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];
13
user349819

Je sais que c’est une vieille question, mais si vous construisez iOS SDK 4+, vous pouvez utiliser des blocs pour le faire avec très peu d’effort et pour le rendre plus lisible:

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];     
});
8
Michael Gaylord

Peut-être que NSValue , assurez-vous que vos pointeurs sont toujours valides après le délai (c'est-à-dire qu'aucun objet n'est alloué sur la pile).

8
Remus Rusanu

PerformSelector: WithObject prend toujours un objet, donc pour passer des arguments comme int/double/float etc ..... Vous pouvez utiliser quelque chose comme ceci.

//NSNumber is an object..

    [self performSelector:@selector(setUserAlphaNumber:) withObject: [NSNumber numberWithFloat: 1.0f]
    afterDelay:1.5];

    -(void) setUserAlphaNumber: (NSNumber*) number{

     [txtUsername setAlpha: [number floatValue] ];
    }

De la même manière, vous pouvez utiliser [NSNumber numberWithInt:] etc .... et dans la méthode de réception, vous pouvez convertir le nombre dans votre format sous le format [nombre entier] ou [nombre double].

6
Augustine

Les blocs sont la voie à suivre. Vous pouvez avoir des paramètres complexes, un type de sécurité, et c'est beaucoup plus simple et sûr que la plupart des anciennes réponses ici. Par exemple, vous pouvez simplement écrire:

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

Les blocs vous permettent de capturer des listes de paramètres arbitraires, des objets de référence et des variables.

Mise en œuvre du support (base):

@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

Exemple:

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;
}

Voir aussi la réponse de Michael (+1) pour un autre exemple.

5
justin

Je recommanderais toujours que vous utilisiez NSMutableArray comme objet à transmettre. En effet, vous pouvez ensuite transmettre plusieurs objets, comme le bouton enfoncé et d’autres valeurs. NSNumber, NSInteger et NSString ne sont que des conteneurs d’une certaine valeur. Assurez-vous que lorsque vous récupérez l'objet du tableau, vous vous référez à un type de conteneur correct. Vous devez transmettre les conteneurs NS. Vous pouvez y tester la valeur. N'oubliez pas que les conteneurs utilisent isEqual lorsque les valeurs sont comparées.

#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]];
    }
}
3
Odd-Arne

Je voulais aussi faire cela, mais avec une méthode qui reçoit un paramètre BOOL. En enveloppant la valeur bool avec NSNumber, FAILED TO PASS THE VALUE. Je ne sais pas pourquoi.

Alors j'ai fini par faire un simple bidouillage. Je mets le paramètre requis dans une autre fonction fictive et appelle cette fonction à l'aide de performSelector, où withObject = nil;

[self performSelector:@selector(dummyCaller:) withObject:nil afterDelay:5.0];

-(void)dummyCaller {

[self myFunction:YES];

}
2
GeneCode

Je trouve que le moyen le plus rapide (mais un peu sale) de faire cela est d'appeler directement objc_msgSend. Cependant, il est dangereux de l'invoquer directement car vous devez lire la documentation et vous assurer que vous utilisez la variante correcte pour le type de valeur de retour et parce que objc_msgSend est défini en tant que vararg pour la commodité du compilateur, mais qu'il est en réalité implémenté en tant que montage rapide. . Voici un code utilisé pour appeler une méthode de délégation - [delegate integerDidChange:] qui prend un seul argument entier.

#import <objc/message.h>


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);
}

Cette première sauvegarde le sélecteur puisque nous allons y faire référence plusieurs fois et il serait facile de créer une faute de frappe. Il vérifie ensuite que le délégué répond effectivement au sélecteur - il peut s'agir d'un protocole facultatif. Il crée ensuite un type de pointeur de fonction qui spécifie la signature réelle du sélecteur. N'oubliez pas que tous les messages Objective-C ont deux premiers arguments cachés, l'objet en cours de messagerie et le sélecteur en cours d'envoi. Ensuite, nous créons un pointeur de fonction du type approprié et le configurons pour qu'il pointe vers la fonction objc_msgSend sous-jacente. Gardez à l'esprit que si la valeur de retour est un float ou une struct, vous devez utiliser une variante différente de objc_msgSend. Enfin, envoyez le message en utilisant la même machine que celle utilisée par Objective-C sous les feuilles.

2
Jason Harris

L'appel de performSelector avec un NSNumber ou une autre NSValue ne fonctionnera pas. Au lieu d'utiliser la valeur de NSValue/NSNumber, il convertira effectivement le pointeur en int, float, ou autre, et l'utilisera.

Mais la solution est simple et évidente. Créer le NSInvocation et appeler

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

1
Anomie

Vous pourriez simplement utiliser NSTimer pour appeler un sélecteur:

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

Peut-être ... ok, très probablement, il me manque quelque chose, mais pourquoi ne pas simplement créer un type d'objet, par exemple NSNumber, en tant que conteneur de votre variable de type non-objet, telle que CGFloat?

CGFloat myFloat = 2.0; 
NSNumber *myNumber = [NSNumber numberWithFloat:myFloat];

[self performSelector:@selector(MyCalculatorMethod:) withObject:myNumber afterDelay:5.0];
0
Jim Hillhouse