Posso passar um bloco como @selector com Objective-C?

É possível passar um bloco Objective-C para o argumento @selector em um UIButton ? ou seja, existe alguma maneira de fazer o seguinte funcionar?

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

obrigado

Sim, mas você teria que usar uma categoria.

Algo como:

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

A implementação seria um pouco mais complicada:

 #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 

Alguma explicação:

  1. Estamos usando uma class “interna apenas” personalizada chamada DDBlockActionWrapper . Esta é uma class simples que possui uma propriedade de bloco (o bloco que queremos chamar), e um método que simplesmente invoca esse bloco.
  2. A categoria UIControl simplesmente instancia um desses wrappers, fornece o bloco a ser invocado e diz a si mesmo para usar esse wrapper e seu método invokeBlock: como o destino e a ação (como normalmente).
  3. A categoria UIControl usa um object associado para armazenar uma matriz de DDBlockActionWrappers , porque o DDBlockActionWrappers não mantém seus destinos. Essa matriz é para garantir que os blocos existam quando devem ser invocados.
  4. Temos que garantir que os DDBlockActionWrappers sejam limpos quando o object é destruído, então estamos fazendo um ataque desagradável de “swizzling out” -[UIControl dealloc] dealoc de UIControl -[UIControl dealloc] com um novo que remove o object associado, e então invoca o código dealloc original. Difícil, complicado. Na verdade, os objects associados são limpos automaticamente durante a desalocação .

Finalmente, esse código foi typescript no navegador e não foi compilado. Há provavelmente algumas coisas erradas com isso. Sua milhagem pode variar.

Blocos são objects. Passe seu bloco como o argumento de target , com @selector(invoke) como o argumento de action , assim:

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

Não, seletores e blocos não são compatíveis no Objective-C (na verdade, são coisas muito diferentes). Você terá que escrever seu próprio método e passar seu seletor.

É possível passar um bloco Objective-C para o argumento @selector em um UIButton?

Tomando em todas as respostas já fornecidas, a resposta é sim, mas um pouco de trabalho é necessário para configurar algumas categorias.

Eu recomendo usar NSInvocation porque você pode fazer muito com isso, como com timeres, armazenados como um object e invocado … etc …

Aqui está o que eu fiz, mas observe que estou usando o ARC.

A primeira é uma categoria simples no NSObject:

.h

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

.m

 #import "Categories.h" #import  @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 

Em seguida é uma categoria em NSInvocation para armazenar em um bloco:

.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 

Aqui está como usá-lo:

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

Você pode fazer muito com a invocação e os methods padrão do Objective-C. Por exemplo, você pode usar NSInvocationOperation (initWithInvocation :), NSTimer (scheduledTimerWithTimeInterval: invocation: repeates 🙂

O ponto é transformar o seu bloco em um NSInvocation é mais versátil e pode ser usado como tal:

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

Mais uma vez, esta é apenas uma sugestão.

Não é tão simples assim, infelizmente.

Em teoria, seria possível definir uma function que adiciona dinamicamente um método à class de target , fazer com que esse método execute o conteúdo de um bloco e retornar um seletor conforme necessário pelo argumento de action . Esta function poderia usar a técnica usada pelo MABlockClosure , que, no caso do iOS, depende de uma implementação customizada do libffi, que ainda é experimental.

É melhor implementar a ação como um método.

A biblioteca BlocksKit no Github (também disponível como CocoaPod) tem esse recurso embutido.

Dê uma olhada no arquivo de header do UIControl + BlocksKit.h. Eles implementaram a ideia de Dave DeLong para que você não precise. Alguma documentação está aqui .

Alguém vai me dizer por que isso está errado, talvez, ou com alguma sorte, talvez não, então vou aprender alguma coisa, ou serei útil.

Eu só joguei isso juntos. É realmente básico, apenas um invólucro fino com um pouco de fundição. Uma palavra de advertência, assume que o bloco que você está invocando tem a assinatura correta para corresponder ao seletor que você usa (isto é, número de argumentos e tipos).

 // // 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 

E

 // // 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 

Não há realmente nada de mágico acontecendo. Apenas muitos downcasting para void * e typecasting para uma assinatura de bloco usável antes de invocar o método. Obviamente (assim como com performSelector: e método associado, as combinações possíveis de inputs são finitas, mas extensíveis se você modificar o código.

Usado assim:

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

Ele produz:

2011-01-03 16: 11: 16.020 BlockInvocation [37096: a0f] O bloco foi invocado com str = teste

Usado em um cenário de ação de destino, você só precisa fazer algo assim:

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

Como o destino em um sistema de ação de destino não é retido, você precisará assegurar que o object de chamada viva pelo tempo que o próprio controle fizer.

Estou interessado em ouvir qualquer coisa de alguém mais experiente que eu.

Eu precisava ter uma ação associada a um UIButton dentro de um UITableViewCell. Eu queria evitar o uso de tags para rastrear cada botão em cada célula diferente. Eu pensei que a maneira mais direta de conseguir isso era associar um bloco “ação” ao botão da seguinte forma:

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

Minha implementação é um pouco mais simplificada, graças ao @bbum por mencionar imp_implementationWithBlock e class_addMethod , (embora não tenha sido extensivamente testado):

 #import  @implementation UIButton (ActionBlock) static int _methodIndex = 0; - (void)addTarget:(id)target withActionBlock:(ActionBlock)block forControlEvent:(UIControlEvents)controlEvents{ if (!target) return; NSString *methodName = [NSString stringWithFormat:@"_blockMethod%d", _methodIndex]; SEL newMethodName = sel_registerName([methodName UTF8String]); 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 

Não funciona para ter um NSBlockOperation (iOS SDK +5). Este código usa o ARC e é uma simplificação de um aplicativo que estou testando com isso (parece funcionar, pelo menos aparentemente, não tenho certeza se estou vazando memory).

 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]; } 

É claro que não tenho certeza de como isso é bom para uso real. Você precisa manter uma referência ao NSBlockOperation vivo ou acho que o ARC irá eliminá-lo.