Puis-je passer un bloc en tant que @selector avec Objective-C?

Est-il possible de passer un bloc Objective-C pour l’argument @selector dans un UIButton ? Par exemple, existe-t-il un moyen d’obtenir les éléments suivants pour travailler?

  [closeOverlayButton addTarget:self action:^ {[anotherIvarLocalToThisMethod removeFromSuperview];} forControlEvents:UIControlEventTouchUpInside]; 

Merci

Oui, mais vous devez utiliser une catégorie.

Quelque chose comme:

 @interface UIControl (DDBlockActions) - (void) addEventHandler:(void(^)(void))handler forControlEvents:(UIControlEvents)controlEvents; @end 

L’implémentation serait un peu plus compliquée:

 #import  @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:

  1. Nous utilisons une classe “internal only” personnalisée appelée DDBlockActionWrapper . Ceci est une classe simple qui a une propriété de bloc (le bloc que nous voulons être appelé) et une méthode qui appelle simplement ce bloc.
  2. La catégorie UIControl instancie simplement l’un de ces wrappers, lui donne le bloc à invoquer, puis se dit d’utiliser ce wrapper et sa méthode invokeBlock: comme cible et action (comme d’habitude).
  3. La catégorie UIControl utilise un object associé pour stocker un tableau de DDBlockActionWrappers , car UIControl ne conserve pas ses cibles. Ce tableau permet de s’assurer que les blocs existent lorsqu’ils sont supposés être invoqués.
  4. Nous devons nous assurer que les DDBlockActionWrappers sont nettoyés lorsque l’object est détruit, nous faisons donc un méchant piratage -[UIControl dealloc] avec un nouveau qui supprime l’object associé, puis appelle le code original de dealloc . Tricky, délicat. En réalité, les objects associés sont automatiquement nettoyés lors de la désallocation .

Enfin, ce code a été saisi dans le navigateur et n’a pas été compilé. Il y a probablement des choses qui ne vont pas. Votre kilométrage peut varier.

Les blocs sont des objects. Passez votre bloc en tant qu’argument target , avec @selector(invoke) comme argument d’ action , comme ceci:

 id block = [^{NSLog(@"Hello, world");} copy];// Don't forget to -release. [button addTarget:block action:@selector(invoke) forControlEvents:UIControlEventTouchUpInside]; 

Non, les sélecteurs et les blocs ne sont pas des types compatibles avec Objective-C (en fait, ils sont très différents). Vous devrez écrire votre propre méthode et passer son sélecteur à la place.

Est-il possible de passer un bloc Objective-C pour l’argument @selector dans un UIButton?

En prenant toutes les réponses déjà fournies, la réponse est oui mais un petit travail est nécessaire pour configurer certaines catégories.

Je recommande d’utiliser NSInvocation car vous pouvez faire beaucoup avec cela, comme avec les timers, stockées en tant qu’object et invoquées … etc …

Voici ce que j’ai fait, mais notez que j’utilise ARC.

First est une catégorie simple sur NSObject:

.h

 @interface NSObject (CategoryNSObject) - (void) associateValue:(id)value withKey:(NSSsortingng *)aKey; - (id) associatedValueForKey:(NSSsortingng *)aKey; @end 

.m

 #import "Categories.h" #import  @implementation NSObject (CategoryNSObject) #pragma mark Associated Methods: - (void) associateValue:(id)value withKey:(NSSsortingng *)aKey { objc_setAssociatedObject( self, (__bridge void *)aKey, value, OBJC_ASSOCIATION_RETAIN ); } - (id) associatedValueForKey:(NSSsortingng *)aKey { return objc_getAssociatedObject( self, (__bridge void *)aKey ); } @end 

Next est une catégorie sur NSInvocation pour stocker dans un bloc:

.h

 @interface NSInvocation (CategoryNSInvocation) + (NSInvocation *) invocationWithTarget:(id)aTarget block:(void (^)(id target))block; + (NSInvocation *) invocationWithSelector:(SEL)aSelector forTarget:(id)aTarget; + (NSInvocation *) invocationWithSelector:(SEL)aSelector andObject:(__autoreleasing id)anObject forTarget:(id)aTarget; @end 

.m

 #import "Categories.h" typedef void (^BlockInvocationBlock)(id target); #pragma mark - Private Interface: @interface BlockInvocation : NSObject @property (readwrite, nonatomic, copy) BlockInvocationBlock block; @end #pragma mark - Invocation Container: @implementation BlockInvocation @synthesize block; - (id) initWithBlock:(BlockInvocationBlock)aBlock { if ( (self = [super init]) ) { self.block = aBlock; } return self; } + (BlockInvocation *) invocationWithBlock:(BlockInvocationBlock)aBlock { return [[self alloc] initWithBlock:aBlock]; } - (void) performWithTarget:(id)aTarget { self.block(aTarget); } @end #pragma mark Implementation: @implementation NSInvocation (CategoryNSInvocation) #pragma mark - Class Methods: + (NSInvocation *) invocationWithTarget:(id)aTarget block:(void (^)(id target))block { BlockInvocation *blockInvocation = [BlockInvocation invocationWithBlock:block]; NSInvocation *invocation = [NSInvocation invocationWithSelector:@selector(performWithTarget:) andObject:aTarget forTarget:blockInvocation]; [invocation associateValue:blockInvocation withKey:@"BlockInvocation"]; return invocation; } + (NSInvocation *) invocationWithSelector:(SEL)aSelector forTarget:(id)aTarget { NSMethodSignature *aSignature = [aTarget methodSignatureForSelector:aSelector]; NSInvocation *aInvocation = [NSInvocation invocationWithMethodSignature:aSignature]; [aInvocation setTarget:aTarget]; [aInvocation setSelector:aSelector]; return aInvocation; } + (NSInvocation *) invocationWithSelector:(SEL)aSelector andObject:(__autoreleasing id)anObject forTarget:(id)aTarget { NSInvocation *aInvocation = [NSInvocation invocationWithSelector:aSelector forTarget:aTarget]; [aInvocation setArgument:&anObject atIndex:2]; return aInvocation; } @end 

Voici comment l’utiliser:

 NSInvocation *invocation = [NSInvocation invocationWithTarget:self block:^(id target) { NSLog(@"TEST"); }]; [invocation invoke]; 

Vous pouvez faire beaucoup avec l’invocation et les méthodes Objective-C standard. Par exemple, vous pouvez utiliser NSInvocationOperation (initWithInvocation :), NSTimer (scheduleTimerWithTimeInterval: invocation: repeates 🙂

Le point est de transformer votre bloc en NSInvocation est plus polyvalent et peut être utilisé en tant que tel:

 NSInvocation *invocation = [NSInvocation invocationWithTarget:self block:^(id target) { NSLog(@"My Block code here"); }]; [button addTarget:invocation action:@selector(invoke) forControlEvents:UIControlEventTouchUpInside]; 

Encore une fois, ce n’est qu’une suggestion.

Pas aussi simple que ça, malheureusement.

En théorie, il serait possible de définir une fonction qui ajoute dynamicment une méthode à la classe de la target , que cette méthode exécute le contenu d’un bloc et renvoie un sélecteur selon les besoins de l’argument action . 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 comme méthode.

La bibliothèque BlocksKit sur Github (également disponible en tant que CocoaPod) a cette fonctionnalité intégrée.

Jetez un coup d’oeil au fichier d’en-tête pour UIControl + BlocksKit.h. Ils ont mis en place l’idée de Dave DeLong pour que vous n’ayez pas à le faire. Une documentation est ici .

Quelqu’un va me dire pourquoi c’est faux ou peut-être pas, alors j’apprendrai ou je serai utile.

Je viens de jeter ça ensemble. C’est vraiment basique, juste un emballage mince avec un peu de moulage. Un mot d’avertissement, il suppose que le bloc que vous appelez a la bonne signature pour correspondre au sélecteur que vous utilisez (c’est-à-dire le nombre d’arguments et de types).

 // // BlockInvocation.h // BlockInvocation // // Created by Chris Corbyn on 3/01/11. // Copyright 2011 __MyCompanyName__. All rights reserved. // #import  @interface BlockInvocation : NSObject { void *block; } -(id)initWithBlock:(void *)aBlock; +(BlockInvocation *)invocationWithBlock:(void *)aBlock; -(void)perform; -(void)performWithObject:(id)anObject; -(void)performWithObject:(id)anObject object:(id)anotherObject; @end 

Et

 // // BlockInvocation.m // BlockInvocation // // Created by Chris Corbyn on 3/01/11. // Copyright 2011 __MyCompanyName__. All rights reserved. // #import "BlockInvocation.h" @implementation BlockInvocation -(id)initWithBlock:(void *)aBlock { if (self = [self init]) { block = (void *)[(void (^)(void))aBlock copy]; } return self; } +(BlockInvocation *)invocationWithBlock:(void *)aBlock { return [[[self alloc] initWithBlock:aBlock] autorelease]; } -(void)perform { ((void (^)(void))block)(); } -(void)performWithObject:(id)anObject { ((void (^)(id arg1))block)(anObject); } -(void)performWithObject:(id)anObject object:(id)anotherObject { ((void (^)(id arg1, id arg2))block)(anObject, anotherObject); } -(void)dealloc { [(void (^)(void))block release]; [super dealloc]; } @end 

Il n’y a vraiment rien de magique. Juste beaucoup de downcasting à void * et de typage vers une signature de bloc utilisable avant d’appeler la méthode. Evidemment (comme avec performSelector: et la méthode associée, les combinaisons possibles d’entrées sont finies, mais extensibles si vous modifiez le code.

Utilisé comme ceci:

 BlockInvocation *invocation = [BlockInvocation invocationWithBlock:^(NSSsortingng *str) { NSLog(@"Block was invoked with str = %@", str); }]; [invocation performWithObject:@"Test"]; 

Il produit:

2011-01-03 16: 11: 16.020 BlockInvocation [37096: a0f] Un bloc a été appelé avec str = Test

Utilisé dans un scénario cible-action, il vous suffit de faire quelque chose comme ceci:

 BlockInvocation *invocation = [[BlockInvocation alloc] initWithBlock:^(id sender) { NSLog(@"Button with title %@ was clicked", [(NSButton *)sender title]); }]; [myButton setTarget:invocation]; [myButton setAction:@selector(performWithObject:)]; 

Étant donné que la cible dans un système d’action cible n’est pas conservée, vous devez vous assurer que l’object d’invocation rest actif aussi longtemps que le contrôle lui-même le fait.

Je suis intéressé à entendre quelque chose de quelqu’un de plus expert que moi.

Je devais avoir une action associée à un UIButton dans un UITableViewCell. Je voulais éviter d’utiliser des tags pour retrouver chaque bouton dans chaque cellule. Je pensais que le moyen le plus direct d’y parvenir était d’associer un bloc “action” au bouton comme ceci:

 [cell.trashButton addTarget:self withActionBlock:^{ NSLog(@"Will remove item #%d from cart!", indexPath.row); ... } forControlEvent:UIControlEventTouchUpInside]; 

Mon implémentation est un peu plus simplifiée grâce à @bbum pour avoir mentionné imp_implementationWithBlock et class_addMethod (bien que n’étant pas testés de manière extensive):

 #import  @implementation UIButton (ActionBlock) static int _methodIndex = 0; - (void)addTarget:(id)target withActionBlock:(ActionBlock)block forControlEvent:(UIControlEvents)controlEvents{ if (!target) return; NSSsortingng *methodName = [NSSsortingng ssortingngWithFormat:@"_blockMethod%d", _methodIndex]; SEL newMethodName = sel_registerName([methodName UTF8Ssortingng]); IMP implementedMethod = imp_implementationWithBlock(block); BOOL success = class_addMethod([target class], newMethodName, implementedMethod, "v@:"); NSLog(@"Method with block was %@", success ? @"added." : @"not added." ); if (!success) return; [self addTarget:target action:newMethodName forControlEvents:controlEvents]; // On to the next method name... ++_methodIndex; } @end 

Cela ne fonctionne-t-il pas d’avoir un NSBlockOperation (iOS SDK +5). Ce code utilise ARC et c’est une simplification d’une application que je teste avec (semble fonctionner, du moins apparemment, pas sûr de perdre de la mémoire).

 NSBlockOperation *blockOp; UIView *testView; -(void) createTestView{ UIView *testView = [[UIView alloc] initWithFrame:CGRectMake(0, 60, 1024, 688)]; testView.backgroundColor = [UIColor blueColor]; [self.view addSubview:testView]; UIButton *btnBack = [UIButton buttonWithType:UIButtonTypeRoundedRect]; [btnBack setFrame:CGRectMake(200, 200, 200, 70)]; [btnBack.titleLabel setText:@"Back"]; [testView addSubview:btnBack]; blockOp = [NSBlockOperation blockOperationWithBlock:^{ [testView removeFromSuperview]; }]; [btnBack addTarget:blockOp action:@selector(start) forControlEvents:UIControlEventTouchUpInside]; } 

Bien sûr, je ne suis pas sûr de la qualité de l’utilisation réelle. Vous devez garder une référence à la NSBlockOperation en vie ou je pense que ARC va le tuer.