Les appels de méthodes dynamiques en Objective-C, notamment via performSelector:, offrent une flexibilité notable par rapport aux appels directs. Contrairement aux appels compilés qui vérifient l'existence de la méthode à la compilation, performSelector: valide cette existence uniquement à l'exécution. Une méthode inexistante entraînera un crash si elle est appelée via performSelector:. Pour pallier ce risque, il est recommandé d'utiliser la méthode respondsToSelector: afin de vérifier la disponibilité de la méthode avant de l'invoquer.
Invocation de méthodes sans paramètre
[self performSelector:@selector(methodeSansParametre)];
// Définition de la méthode
- (void)methodeSansParametre {
NSLog(@"Exécution de la méthode sans paramètre");
}
Invocation de méthodes avec un paramètre
[self performSelector:@selector(methodeAvecUnParametre:) withObject:@"Premier paramètre"];
// Définition de la méthode
- (void)methodeAvecUnParametre:(NSString *)parametre {
NSLog(@"Paramètre reçu : %@", parametre);
}
Invocation de méthodes avec deux paramètres
L'invocation direccte de méthodes avec plus de deux paramètres via performSelector:withObject:withObject: n'est pas supportée nativement. Cependant, il est possible de contourner cette limitation en encapsulant les arguments supplémentaires.
Création et invocation de méthodes dynamiques
Il est possible de construire dynamiquement des appels de méthodes en utilisant des chaînes de caractères pour les noms de méthodes.
NSArray *configurationsAppel = @[
@{@"nomMethode": @"traiterChaine:", @"valeur": @"Texte dynamique"},
@{@"nomMethode": @"traiterNombre:", @"valeur": @42}
];
for (NSDictionary *config in configurationsAppel) {
SEL s = NSSelectorFromString(config[@"nomMethode"]);
id valeur = config[@"valeur"];
[self performSelector:s withObject:valeur];
}
// Définition des méthodes dynamiques
- (void)traiterChaine:(NSString *)chaine {
NSLog(@"Traitement chaîne : %@", chaine);
}
- (void)traiterNombre:(NSNumber *)nombre {
NSLog(@"Traitement nombre : %@", nombre);
}
D'autres méthodes performSelector existent pour gérer l'exécution sur des threads spécifiques :
performSelectorOnMainThread:withObject:waitUntilDone:: exécute la méthode sur le thread principal. SiwaitUntilDoneestYES, le thread appelant sera bloqué jusqu'à la fin de l'exécution.
Gestion des appels avec trois paramètres ou plus
Pour transmettre trois arguments ou plus, plusieurs stratégies s'offrent à vous :
- Utilisation de
NSInvocation: Cette approche, basée sur le mécanisme de réflexion de Runtime, permet de construire une description complète de l'appel de méthode, y compris les paramètres. Bien qu'efficace, elle peut être moins lisible et légèrement plus coûteuse en performance que d'autres méthodes.
- (id)invoquerMethodeAvecObjets:(SEL)selecteur avecArguments:(NSArray *)arguments {
NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:selecteur];
if (!signature) {
// Gérer l'absence de signature, par exemple en lançant une exception.
return nil;
}
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = self;
invocation.selector = selecteur;
NSUInteger nombreArgumentsReels = signature.numberOfArguments - 2; // Exclut 'self' et '_cmd'
NSUInteger nombreArgumentsAPasser = MIN(nombreArgumentsReels, arguments.count);
for (NSUInteger i = 0; i < nombreArgumentsAPasser; i++) {
id argument = arguments[i];
// Ignorer les valeurs nil, sauf si la méthode attend spécifiquement un objet nul.
if ([argument isKindOfClass:[NSNull class]]) continue;
[invocation setArgument:&argument atIndex:(i + 2)];
}
[invocation invoke];
id resultat = nil;
if (signature.methodReturnLength > 0) {
[invocation getReturnValue:&resultat];
}
return resultat;
}
// Exemple d'utilisation
NSString *texte = @"Données textuelles";
NSNumber *chiffre = @100;
NSArray *liste = @[@"Élément 1", @"Élément 2"];
SEL selSpecifique = NSSelectorFromString(@"methodeComplexe:avecNombre:etListe:");
NSArray *argumentsAppel = @[texte, chiffre, liste];
[self performSelector:selSpecifique withObjects:argumentsAppel];
// Définition de la méthode cible
- (void)methodeComplexe:(NSString *)texte avecNombre:(NSNumber *)chiffre etListe:(NSArray *)liste {
NSLog(@"Texte: %@, Nombre: %@, Premier élément liste: %@", texte, chiffre, liste.firstObject);
}
- Encapsulation des paramètres: Regrouper plusieurs paramètres en une seule structure de données, comme un
NSDictionaryou un objet personnalisé, puis passer cette structure unique. Cette méthode améliore la lisibilité. - Utilisation de
objc_msgSend: Bien queperformSelector:utiliseobjc_msgSenden interne pour la distribution des messages, l'appel direct viaobjc_msgSendpermet de passer un nombre arbitraire d'arguments, y compris des structures, directement. Il faut cependant être prudent car cette méthode n'effectue pas les vérifications de sécurité habituelles.
#import <objc/message.h>
// Exemple d'utilisation
NSString *texteMsgSend = @"Message send";
NSNumber *nombreMsgSend = @50;
NSArray *listeMsgSend = @[@"A", @"B"];
SEL selMsgSend = NSSelectorFromString(@"methodeMsgSend:nombre:liste:");
// Casting de objc_msgSend pour correspondre à la signature de la méthode
void (*fonctionMsgSend)(id, SEL, NSString *, NSNumber *, NSArray *) = (void (*) (id, SEL, NSString *, NSNumber *, NSArray *))objc_msgSend;
fonctionMsgSend(self, selMsgSend, texteMsgSend, nombreMsgSend, listeMsgSend);
// Définition de la méthode cible
- (void)methodeMsgSend:(NSString *)texte nombre:(NSNumber *)nombre liste:(NSArray *)liste {
NSLog(@"MsgSend - Texte: %@, Nombre: %@, Premier élément liste: %@", texte, nombre, liste.firstObject);
}
Passage de structures comme paramètres
Pour passer des structures, elles doivent être encapsulées dans un objet NSValue.
typedef struct {
int x;
int y;
} Point2D;
// Exemple d'utilisation
NSString *description = @"Passage de structure";
NSNumber *valeur = @10;
Point2D coords = {100, 200};
NSValue *valeurStructure = [NSValue valueWithBytes:&coords objCType:@encode(Point2D)];
SEL selStructure = NSSelectorFromString(@"methodeAvecStructure:valeur:point:");
NSArray *argumentsStructure = @[description, valeur, valeurStructure];
[self performSelector:selStructure withObjects:argumentsStructure];
// Définition de la méthode cible
- (void)methodeAvecStructure:(NSString *)desc valeur:(NSNumber *)val point:(NSValue *)valeurPoint {
Point2D pointRecupere;
[valeurPoint getValue:&pointRecupere];
NSLog(@"Description: %@, Valeur: %@, Point X: %d, Point Y: %d", desc, val, pointRecupere.x, pointRecupere.y);
}
Retardement de l'exécution d'une méthode
La méthode performSelector:withObject:afterDelay: ajoute un timer à la boucle d'événements courante. L'exécution de la méthode sélectionnée est donc différée et se produit après que le bloc de code courant soit terminé.
// Méthode principale
- (void)executerAvecDelai {
[self performSelector:@selector(methodeDifferree) withObject:nil afterDelay:1.0];
NSLog(@"Appel de performSelector avec délai initié.");
// Le code ici s'exécute immédiatement
NSLog(@"Fin du bloc d'exécution courant.");
}
// Méthode différée
- (void)methodeDifferree {
NSLog(@"Exécution de la méthode différée.");
}
// Si executerAvecDelai est appelé, l'ordre des logs sera :
// Appel de performSelector avec délai initié.
// Fin du bloc d'exécution courant.
// Exécution de la méthode différée. (après 1 seconde)
Pour une exécution immédiate sans attendre la fin du bloc courant, il faut éviter l'utilisation de afterDelay:. L'utilisation de performSelectorInBackground: exécute la méthode sur un thread séparé, permettant une exécution concurrente.
performSelectorOnMainThread:withObject:waitUntilDone: permet de s'assurer que la méthode s'exécute sur le thread principal. Si waitUntilDone est YES, l'appelant attend la fin de l'exécution ; sinon, il continue immédiatement.
Pour simuler un délai sans bloquer le thread courant, dispatch_after est une alternative moderne et plus flexible, souvent utilisée avec des files d'exécution globales.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
if ([self respondsToSelector:@selector(methodeDifferree)]) {
[self performSelector:@selector(methodeDifferree) withObject:nil];
}
});
NSLog(@"Dispatch_after configuré.");
// Le reste du code s'exécute immédiatement.
Problèmes d'exécution dans les threads secondaires
L'utilisation de performSelector:withObject:afterDelay: dans un thread secondaire peut échouer car les threads secondaires n'ont pas de boucle d'événements (RunLoop) par défaut. Pour y remédier, il faut initialiser la boucle d'événements du thread secondaire avant d'utiliser afterDelay:.
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// Initialiser la boucle d'événements du thread courant
[NSRunLoop currentRunLoop];
// Maintenant, performSelector:afterDelay: fonctionnera
[self performSelector:@selector(methodeDifferree) withObject:nil afterDelay:0];
[[NSRunLoop currentRunLoop] run]; // Démarrer la boucle d'événements
});
Une alternative plus simple consiste à utiliser dispatch_after pour planifier l'exécution sur un thread global, comme montré précédemment.
Prévension des clics répétés sur des boutons
Pour éviter les actions multiples dues à des clics rapides sur un bouton, on peut utiliser cancelPreviousPerformRequestsWithTarget:selector:object: pour annuler les appels planifiés précédemment et en programmer un nouveau avec un léger délai.
-(void)actionClicBouton:(UIButton *)sender {
// Annuler toute requête d'exécution précédemment planifiée pour ce sélecteur et cet objet
[[self class] cancelPreviousPerformRequestsWithTarget:self
selector:@selector(traitementClicBouton:)
object:sender];
// Planifier une nouvelle exécution après un court délai
[self performSelector:@selector(traitementClicBouton:) withObject:sender afterDelay:0.2f];
}
-(void)traitementClicBouton:(UIButton *)sender {
NSLog(@"Traitement du clic sur le bouton.");
// Logique de traitement du clic...
}
Une autre approche consiste à désactiver le bouton pendant une courte période après le clic.
-(void)actionClicBoutonSimple:(id)sender {
self.bouton.enabled = NO; // Désactiver le bouton
// Planifier la réactivation du bouton après 1 seconde
[self performSelector:@selector(reactiverBouton) withObject:nil afterDelay:1.0f];
}
-(void)reactiverBouton {
self.bouton.enabled = YES; // Réactiver le bouton
}