iOS - Comment implémenter un performSelector avec plusieurs arguments et avec afterDelay?

90

Je suis un débutant iOS. J'ai une méthode de sélection comme suit -

- (void) fooFirstInput:(NSString*) first secondInput:(NSString*) second
{

}

J'essaye de mettre en œuvre quelque chose comme ça -

[self performSelector:@selector(fooFirstInput:secondInput:) withObject:@"first" withObject:@"second" afterDelay:15.0];

Mais cela me donne une erreur en disant -

Instance method -performSelector:withObject:withObject:afterDelay: not found

Des idées sur ce qui me manque?

Suchi
la source

Réponses:

142

Personnellement, je pense qu'une solution plus proche de vos besoins est l'utilisation de NSInvocation.

Quelque chose comme ce qui suit fera le travail:

indexPath et dataSource sont deux variables d'instance définies dans la même méthode.

SEL aSelector = NSSelectorFromString(@"dropDownSelectedRow:withDataSource:");

if([dropDownDelegate respondsToSelector:aSelector]) {
    NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[dropDownDelegate methodSignatureForSelector:aSelector]];
    [inv setSelector:aSelector];
    [inv setTarget:dropDownDelegate];

    [inv setArgument:&(indexPath) atIndex:2]; //arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation
    [inv setArgument:&(dataSource) atIndex:3]; //arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation

    [inv invoke];
}
valvoline
la source
2
D'accord. Ce devrait être la bonne réponse. Solution très utile. Surtout dans mon cas où il n'est pas permis de changer la signature de la méthode contenant plusieurs arguments.
AbhijeetMishra
Cela ressemble à une excellente solution. Existe-t-il un moyen d'obtenir une valeur de retour de la méthode appelée avec cette technique?
David Pettigrew
15
Comment spécifiez-vous le délai avec cette technique?
death_au
4
@death_au, juste au lieu d' invokeappeler: [inv performSelector:@selector(invoke) withObject:nil afterDelay:1]; je dois convenir que c'est une excellente solution. Bon codage à tous!
Maxim Chetrusca
2
Un peu tard à la conversation, mais j'ai une question. Qu'est-ce que dropDownDelegate?
Minestrone-Soup
98

Parce qu'il n'y a pas de [NSObject performSelector:withObject:withObject:afterDelay:]méthode.

Vous devez encapsuler les données que vous souhaitez envoyer dans un objet Objective C unique (par exemple un NSArray, un NSDictionary, un type Objective C personnalisé), puis les transmettre via la [NSObject performSelector:withObject:afterDelay:]méthode bien connue et appréciée.

Par exemple:

NSArray * arrayOfThingsIWantToPassAlong = 
    [NSArray arrayWithObjects: @"first", @"second", nil];

[self performSelector:@selector(fooFirstInput:) 
           withObject:arrayOfThingsIWantToPassAlong  
           afterDelay:15.0];
Michael Dautermann
la source
Je n'obtiens pas d'erreur si je supprime le paramètre afterDelay. Cela signifie-t-il que AfterDelay ne peut pas être utilisé avec plus d'un paramètre?
Suchi
1
vous n'obtenez pas d'erreur, mais je parie que vous obtiendrez une exception "sélecteur non trouvé" au moment de l'exécution (et la chose que vous essayez d'exécuter ne serait pas appelée) ... essayez-la et voyez. :-)
Michael Dautermann
Comment passer le type Bool ici?
virata
Faites-en un objet de style Objective C (par exemple " NSNumber * whatToDoNumber = [NSNumber numberWithBool: doThis];") et passez-le en tant que paramètre unique, @virata.
Michael Dautermann
2
c'est une question distincte @Raj ... veuillez la poster séparément.
Michael Dautermann
34

Vous pouvez regrouper vos paramètres dans un seul objet et utiliser une méthode d'assistance pour appeler votre méthode d'origine comme Michael et d'autres l'ont suggéré.

Une autre option est dispatch_after, qui prendra un bloc et le mettra en file d'attente à un certain moment.

double delayInSeconds = 15.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);

dispatch_after(popTime, dispatch_get_main_queue(), ^(void){

    [self fooFirstInput:first secondInput:second];

});

Ou, comme vous l'avez déjà découvert, si vous n'avez pas besoin du délai, vous pouvez simplement utiliser - performSelector:withObject:withObject:

Firoze Lafeer
la source
Ce qui est également bon à propos de cette approche, c'est que vous pouvez utiliser __weakpour donner à votre prétendu minuteur seulement un lien faible vers soi-même - de sorte que vous ne finissez pas par prolonger artificiellement le cycle de vie de votre objet et par exemple si votre performSelector: afterDelay: effectue quelque chose d'un peu comme tail récursivité (bien que sans la récursivité) puis il résout le cycle de rétention.
Tommy
1
Oui, cela devrait être la réponse acceptée. C'est plus approprié et simple.
Roohul
7

L'option la plus simple consiste à modifier votre méthode pour prendre un seul paramètre contenant les deux arguments, comme un NSArrayou NSDictionary(ou ajouter une deuxième méthode qui prend un seul paramètre, le décompresse et appelle la première méthode, puis appelez la deuxième méthode sur un retard).

Par exemple, vous pourriez avoir quelque chose comme:

- (void) fooOneInput:(NSDictionary*) params {
    NSString* param1 = [params objectForKey:@"firstParam"];
    NSString* param2 = [params objectForKey:@"secondParam"];
    [self fooFirstInput:param1 secondInput:param2];
}

Et puis pour l'appeler, vous pouvez faire:

[self performSelector:@selector(fooOneInput:) 
      withObject:[NSDictionary dictionaryWithObjectsAndKeys: @"first", @"firstParam", @"second", @"secondParam", nil] 
      afterDelay:15.0];
aroth
la source
Et si la méthode ne peut pas être modifiée, disons qu'elle vit dans UIKit ou quelque chose comme ça? Non seulement cela, changer la méthode à utiliser NSDictionaryperd également la sécurité du type. Pas idéal.
fatuhoku
@fatuhoku - C'est couvert par la parenthèse; "ajouter une deuxième méthode qui prend un seul paramètre, le décompresse et appelle la première méthode". Cela fonctionne quel que soit l' endroit où se trouve la première méthode. Quant à la sécurité de type, elle a été perdue au moment où la décision a été prise d'utiliser performSelector:(ou NSInvocation). Si c'est un problème, la meilleure option serait probablement de passer par GCD.
aroth
6
- (void) callFooWithArray: (NSArray *) inputArray
{
    [self fooFirstInput: [inputArray objectAtIndex:0] secondInput: [inputArray objectAtIndex:1]];
}


- (void) fooFirstInput:(NSString*) first secondInput:(NSString*) second
{

}

et appelez-le avec:

[self performSelector:@selector(callFooWithArray) withObject:[NSArray arrayWithObjects:@"first", @"second", nil] afterDelay:15.0];
Mike Wallace
la source
5

Vous pouvez trouver tous les types de méthodes performSelector: fournies ici:

http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/nsobject_Class/Reference/Reference.html

Il existe de nombreuses variantes, mais il n'y a pas de version qui accepte plusieurs objets ainsi qu'un retard. Vous devrez à la place encapsuler vos arguments dans un NSArray ou NSDictionary.

- performSelector:
- performSelector:withObject:
- performSelector:withObject:withObject:
 performSelector:withObject:afterDelay:
 performSelector:withObject:afterDelay:inModes:
 performSelectorOnMainThread:withObject:waitUntilDone:
 performSelectorOnMainThread:withObject:waitUntilDone:modes:
 performSelector:onThread:withObject:waitUntilDone:
 performSelector:onThread:withObject:waitUntilDone:modes:
 performSelectorInBackground:withObject: 
StilesCrisis
la source
2

Je n'aime pas la méthode NSInvocation, trop complexe. Gardons les choses simples et propres:

// Assume we have these variables
id target, SEL aSelector, id parameter1, id parameter2;

// Get the method IMP, method is a function pointer here.
id (*method)(id, SEL, id, id) = (void *)[target methodForSelector:aSelector];

// IMP is just a C function, so we can call it directly.
id returnValue = method(target, aSelector, parameter1, parameter2);
BB9z
la source
Agréable! Remplacez le `` vc '' par `` cible ''
Anton
1

J'ai juste fait quelques swizzling et ai dû appeler la méthode originale. Ce que j'ai fait, c'est de faire un protocole et de m'y opposer. Une autre façon est de définir la méthode dans une catégorie, mais nécessiterait la suppression d'un avertissement (#pragma clang diagnostic ignoré "-Wincomplete-implementation").

darkfader
la source
0

Un moyen simple et réutilisable est d'étendre NSObjectet de mettre en œuvre

- (void)performSelector:(SEL)aSelector withObjects:(NSArray *)arguments;

quelque chose comme:

- (void)performSelector:(SEL)aSelector withObjects:(NSArray *)arguments
{
    NSMethodSignature *signature = [self methodSignatureForSelector: aSelector];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature: signature];
    [invocation setSelector: aSelector];

    int index = 2; //0 and 1 reserved
    for (NSObject *argument in arguments) {
        [invocation setArgument: &argument atIndex: index];
        index ++;
    }
    [invocation invokeWithTarget: self];
}
Kappe
la source
0

Je créerais simplement un objet personnalisé contenant tous mes paramètres en tant que propriétés, puis utiliserais cet objet unique comme paramètre

MobileMon
la source