Oui, mais vous devrez utiliser une catégorie.
Quelque chose comme:
@interface UIControl (DDBlockActions)
- (void) addEventHandler:(void(^)(void))handler
forControlEvents:(UIControlEvents)controlEvents;
@end
La mise en œuvre serait un peu plus délicate:
#import <objc/runtime.h>
@interface DDBlockActionWrapper : NSObject
@property (nonatomic, copy) void (^blockAction)(void);
- (void) invokeBlock:(id)sender;
@end
@implementation DDBlockActionWrapper
@synthesize blockAction;
- (void) dealloc {
[self setBlockAction:nil];
[super dealloc];
}
- (void) invokeBlock:(id)sender {
[self blockAction]();
}
@end
@implementation UIControl (DDBlockActions)
static const char * UIControlDDBlockActions = "unique";
- (void) addEventHandler:(void(^)(void))handler
forControlEvents:(UIControlEvents)controlEvents {
NSMutableArray * blockActions =
objc_getAssociatedObject(self, &UIControlDDBlockActions);
if (blockActions == nil) {
blockActions = [NSMutableArray array];
objc_setAssociatedObject(self, &UIControlDDBlockActions,
blockActions, OBJC_ASSOCIATION_RETAIN);
}
DDBlockActionWrapper * target = [[DDBlockActionWrapper alloc] init];
[target setBlockAction:handler];
[blockActions addObject:target];
[self addTarget:target action:@selector(invokeBlock:) forControlEvents:controlEvents];
[target release];
}
@end
Quelques explications:
- Nous utilisons une classe personnalisée "interne uniquement" appelée
DDBlockActionWrapper
. Il s'agit d'une classe simple qui a une propriété de bloc (le bloc que nous voulons invoquer) et une méthode qui appelle simplement ce bloc.
- La
UIControl
catégorie instancie simplement l'un de ces wrappers, lui donne le bloc à invoquer, puis se dit d'utiliser ce wrapper et sesinvokeBlock:
méthode comme cible et action (comme d'habitude).
- La
UIControl
catégorie utilise un objet associé pour stocker un tableau de DDBlockActionWrappers
, car UIControl
ne conserve pas ses cibles. Ce tableau permet de garantir que les blocs existent lorsqu'ils sont censés être appelés.
Nous devons nous assurer que le DDBlockActionWrappers
nettoyage est effectué lorsque l'objet est détruit, nous faisons donc un vilain piratage de swizzling -[UIControl dealloc]
avec un nouveau qui supprime l'objet associé, puis invoque le dealloc
code d' origine . Tricky, délicat. En fait, les objets associés sont nettoyés automatiquement lors de la désallocation .
Enfin, ce code a été tapé dans le navigateur et n'a pas été compilé. Il y a probablement des problèmes avec cela. Votre kilométrage peut varier.
objc_implementationWithBlock()
etclass_addMethod()
résoudre ce problème de manière légèrement plus efficace que d'utiliser des objets associés (ce qui implique une recherche de hachage qui n'est pas aussi efficace que la recherche de méthode). Probablement une différence de performance non pertinente, mais c'est une alternative.imp_implementationWithBlock
?objc_implementationWithBlock()
. :)UITableViewCell
entraînera la duplication des actions cibles souhaitées puisque chaque nouvelle cible est une nouvelle instance et que les précédentes ne sont pas nettoyées pour les mêmes événements. Vous devez d'abord nettoyer les ciblesfor (id t in self.allTargets) { [self removeTarget:t action:@selector(invokeBlock:) forControlEvents:controlEvents]; } [self addTarget:target action:@selector(invokeBlock:) forControlEvents:controlEvents];
Les blocs sont des objets. Passez votre bloc comme
target
argument, avec@selector(invoke)
commeaction
argument, comme ceci:la source
invoke
méthode sur les objets Block n'est pas publique et n'est pas destinée à être utilisée de cette manière.nil
au lieu de@selector(invoke)
.Non, les sélecteurs et les blocs ne sont pas des types compatibles en Objective-C (en fait, ce sont des choses très différentes). Vous devrez écrire votre propre méthode et passer son sélecteur à la place.
la source
En tenant compte de toutes les réponses déjà fournies, la réponse est Oui, mais un peu de travail est nécessaire pour configurer certaines catégories.
Je recommande d'utiliser NSInvocation car vous pouvez faire beaucoup avec cela, comme avec des minuteries, stockées en tant qu'objet et invoquées ... etc ...
Voici ce que j'ai fait, mais notez que j'utilise ARC.
Le premier est une catégorie simple sur NSObject:
.h
.m
Vient ensuite une catégorie sur NSInvocation à stocker dans un bloc:
.h
.m
Voici comment l'utiliser:
Vous pouvez faire beaucoup avec l'invocation et les méthodes Objective-C standard. Par exemple, vous pouvez utiliser NSInvocationOperation (initWithInvocation :), NSTimer (planifiéTimerWithTimeInterval: invocation: repeates :)
Le but est de transformer votre bloc en NSInvocation est plus polyvalent et peut être utilisé comme tel:
Encore une fois, ce n'est qu'une suggestion.
la source
Pas aussi simple que ça, malheureusement.
En théorie, il serait possible de définir une fonction qui ajoute dynamiquement une méthode à la classe de
target
, que cette méthode exécute le contenu d'un bloc et renvoie un sélecteur selon les besoins de l'action
argument. Cette fonction pourrait utiliser la technique utilisée par MABlockClosure , qui, dans le cas d'iOS, dépend d'une implémentation personnalisée de libffi, qui est encore expérimentale.Il vaut mieux mettre en œuvre l'action en tant que méthode.
la source
La bibliothèque BlocksKit sur Github (également disponible en CocoaPod) a cette fonctionnalité intégrée.
Jetez un œil au fichier d'en-tête pour UIControl + BlocksKit.h. Ils ont mis en œuvre l'idée de Dave DeLong pour que vous n'ayez pas à le faire. Une documentation est ici .
la source
Quelqu'un va me dire pourquoi c'est faux, peut-être, ou avec un peu de chance, peut-être pas, alors j'apprendrai quelque chose ou je serai utile.
J'ai juste jeté ça ensemble. C'est vraiment basique, juste une fine enveloppe avec un peu de moulage. Un mot d'avertissement, cela suppose que le bloc que vous invoquez a la signature correcte pour correspondre au sélecteur que vous utilisez (c'est-à-dire le nombre d'arguments et de types).
Et
Il ne se passe vraiment rien de magique. Juste beaucoup de downcasting
void *
et de typage vers une signature de bloc utilisable avant d'appeler la méthode. Évidemment (tout comme avecperformSelector:
et méthode associée, les combinaisons d'entrées possibles sont finies, mais extensibles si vous modifiez le code.Utilisé comme ceci:
Il sort:
Utilisé dans un scénario d'action cible, il vous suffit de faire quelque chose comme ceci:
Étant donné que la cible dans un système d'action-cible n'est pas conservée, vous devrez vous assurer que l'objet d'appel dure aussi longtemps que le contrôle lui-même.
Je suis intéressé d'entendre quelque chose de quelqu'un de plus expert que moi.
la source
invocation
n'est jamais publiéJ'avais besoin d'avoir une action associée à un UIButton dans un UITableViewCell. Je voulais éviter d'utiliser des balises pour localiser chaque bouton dans chaque cellule différente. Je pensais que le moyen le plus direct pour y parvenir était d'associer un bloc "action" au bouton comme ceci:
Mon implémentation est un peu plus simplifiée, grâce à @bbum pour avoir mentionné
imp_implementationWithBlock
etclass_addMethod
, (bien que pas largement testé):la source
Cela ne fonctionne pas d'avoir une NSBlockOperation (iOS SDK +5). Ce code utilise ARC et c'est une simplification d'une application avec laquelle je teste cela (semble fonctionner, du moins apparemment, je ne sais pas si je fuit de la mémoire).
Bien sûr, je ne sais pas à quel point c'est bon pour un usage réel. Vous devez garder une référence à NSBlockOperation vivante ou je pense que l'ARC le tuera.
la source