90 votes

Puis-je passer un bloc comme @sélecteur en Objective-C ?

Est-il possible de passer un bloc Objective-C pour la fonction @selector argument dans un UIButton ? c'est-à-dire, y a-t-il un moyen de faire fonctionner ce qui suit ?

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

Gracias

69voto

Dave DeLong Points 156978

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

Une explication :

  1. Nous utilisons une classe personnalisée "interne seulement" appelée DDBlockActionWrapper . Il s'agit d'une classe simple qui possède une propriété de bloc (le bloc que nous voulons invoquer), et une méthode qui invoque simplement ce bloc.
  2. El UIControl instancie simplement l'un de ces wrappers, lui donne le bloc à invoquer, puis se dit qu'il faut utiliser ce wrapper et sa fonction invokeBlock: comme cible et action (comme d'habitude).
  3. El UIControl utilise un objet associé pour stocker un tableau d'éléments de la catégorie DDBlockActionWrappers parce que UIControl ne conserve pas ses cibles. Ce tableau permet de s'assurer que les blocs existent lorsqu'ils sont censés être invoqués.
  4. Nous devons nous assurer que le DDBlockActionWrappers sont nettoyés lorsque l'objet est détruit, donc nous faisons un méchant piratage en éliminant -[UIControl dealloc] avec un nouveau qui supprime l'objet associé, puis invoque l'objet original dealloc code. C'est délicat, 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 choses qui ne vont pas. Votre kilométrage peut varier.

41voto

lemnar Points 3108

Les blocs sont des objets. Passez votre bloc en tant que target argument, avec @selector(invoke) comme le action argument, comme ceci :

id block = [^{NSLog(@"Hello, world");} copy];// Don't forget to -release.

[button addTarget:block
           action:@selector(invoke)
 forControlEvents:UIControlEventTouchUpInside];

17voto

BoltClock Points 249668

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.

7voto

Arvin Points 1392

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

En tenant compte de 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 l'utilisation de NSInvocation parce que vous pouvez faire beaucoup de choses avec cela, comme avec les timers, stockés comme un objet et invoqués...etc....

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

La première est une simple catégorie sur NSObject :

.h

@interface NSObject (CategoryNSObject)

- (void) associateValue:(id)value withKey:(NSString *)aKey;
- (id) associatedValueForKey:(NSString *)aKey;

@end

.m

#import "Categories.h"
#import <objc/runtime.h>

@implementation NSObject (CategoryNSObject)

#pragma mark Associated Methods:

- (void) associateValue:(id)value withKey:(NSString *)aKey {

    objc_setAssociatedObject( self, (__bridge void *)aKey, value, OBJC_ASSOCIATION_RETAIN );
}

- (id) associatedValueForKey:(NSString *)aKey {

    return objc_getAssociatedObject( self, (__bridge void *)aKey );
}

@end

Ensuite, il y a une catégorie sur NSInvocation à 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 de choses avec l'invocation et les méthodes Objective-C standard. Par exemple, vous pouvez utiliser NSInvocationOperation (initWithInvocation :), NSTimer (scheduledTimerWithTimeInterval:invocation:repeats :)

Le fait de transformer votre bloc en une NSInvocation est plus polyvalent et peut être utilisé comme 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.

5voto

Bavarious Points 43993

Ce n'est pas aussi simple que cela, malheureusement.

En théorie, il serait possible de définir une fonction qui ajoute dynamiquement une méthode à la classe de target de faire en sorte que cette méthode exécute le contenu d'un bloc et renvoie un sélecteur selon les besoins de l'application 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 est préférable d'implémenter l'action comme une méthode.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X