¿Puedo pasar un bloque como @selector con Objective-C?

¿Es posible pasar un bloque Objective-C para el argumento @selector en un UIButton ? es decir, ¿hay alguna forma de hacer que lo siguiente funcione?

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

Gracias

Sí, pero tendrías que usar una categoría.

Algo como:

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

La implementación sería un poco más 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 

Alguna explicación:

  1. Estamos utilizando una clase personalizada “solo interna” llamada DDBlockActionWrapper . Esta es una clase simple que tiene una propiedad de bloque (el bloque que queremos invocar), y un método que simplemente invoca ese bloque.
  2. La categoría UIControl simplemente instancia una de estas envolturas, le da el bloque que se invocará y luego se dice a sí mismo que use esa envoltura y su método invokeBlock: como objective y acción (como es normal).
  3. La categoría UIControl usa un objeto asociado para almacenar una matriz de DDBlockActionWrappers , porque UIControl no conserva sus objectives. Esta matriz es para garantizar que los bloques existan cuando se supone que deben invocarse.
  4. Tenemos que asegurarnos de que los DDBlockActionWrappers se limpien cuando se destruye el objeto, por lo que estamos haciendo un desagradable truco de desvanecimiento -[UIControl dealloc] con uno nuevo que elimina el objeto asociado, y luego invoca el código dealloc original. Tricky, tramposo. En realidad, los objetos asociados se limpian automáticamente durante la desasignación .

Finalmente, este código fue escrito en el navegador y no ha sido comstackdo. Probablemente hay algunas cosas mal con eso. Su experiencia puede ser diferente.

Los bloques son objetos. Pase su bloque como el argumento de target , con @selector(invoke) como argumento de action , como este:

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

No, los selectores y los bloques no son tipos compatibles en Objective-C (de hecho, son cosas muy diferentes). Tendrás que escribir tu propio método y pasar su selector en su lugar.

¿Es posible pasar un bloque Objective-C para el argumento @selector en un UIButton?

Tomando todas las respuestas ya proporcionadas, la respuesta es Sí, pero es necesario un poco de trabajo para configurar algunas categorías.

Recomiendo usar NSInvocation porque puedes hacer mucho con esto, como con temporizadores, almacenar como un objeto e invocar … etc …

Esto es lo que hice, pero tenga en cuenta que estoy usando ARC.

Primero es una categoría simple en NSObject:

.h

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

.metro

 #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 

A continuación se muestra una categoría de NSInvocation para almacenar en un bloque:

.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 

.metro

 #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 

Aquí es cómo usarlo:

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

Puede hacer mucho con la invocación y los métodos Objective-C estándar. Por ejemplo, puede usar NSInvocationOperation (initWithInvocation :), NSTimer (scheduledTimerWithTimeInterval: invocation: repeates 🙂

El punto es convertir su bloque en una NSInvocación es más versátil y se puede utilizar como tal:

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

De nuevo, esta es solo una sugerencia.

No tan simple como eso, desafortunadamente.

En teoría, sería posible definir una función que agregue dinámicamente un método a la clase de target , hacer que ese método ejecute el contenido de un bloque y devolver un selector según lo necesite el argumento de action . Esta función podría utilizar la técnica utilizada por MABlockClosure , que, en el caso de iOS, depende de una implementación personalizada de libffi, que todavía es experimental.

Es mejor implementar la acción como método.

La biblioteca BlocksKit en Github (también disponible como CocoaPod) tiene esta función incorporada.

Eche un vistazo al archivo de encabezado de UIControl + BlocksKit.h. Han implementado la idea de Dave DeLong para que no tenga que hacerlo. Alguna documentación está aquí .

Alguien me va a decir por qué esto está mal, tal vez, o con un poco de suerte, tal vez no, así que aprenderé algo o seré útil.

Solo lancé esto juntos. Es realmente básico, solo una envoltura delgada con un poco de fundición. Una palabra de advertencia, se supone que el bloque que invoca tiene la firma correcta para que coincida con el selector que utiliza (es decir, el número de argumentos y 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 

Y

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

Realmente no hay nada mágico pasando. Solo un montón de downcasting para void * y encasillar a una firma de bloque utilizable antes de invocar el método. Obviamente (al igual que con performSelector: y el método asociado, las posibles combinaciones de entradas son finitas, pero ampliables si modifica el código.

Usado así:

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

Emite:

2011-01-03 16: 11: 16.020 BlockInvocation [37096: a0f] Se invoca el bloque con str = Test

Utilizado en un escenario de acción objective, solo tiene que hacer algo como esto:

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

Dado que el objective en un sistema de acción objective no se conserva, deberá asegurarse de que el objeto de invocación viva durante el tiempo que lo haga el control.

Estoy interesado en escuchar algo de alguien más experto que yo.

Necesitaba tener una acción asociada a un UIButton dentro de una UITableViewCell. Quería evitar el uso de tags para rastrear cada botón en cada celda diferente. Pensé que la forma más directa de lograr esto era asociar una “acción” de bloques al botón como esta:

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

Mi implementación es un poco más simplificada, gracias a @bbum por mencionar imp_implementationWithBlock y class_addMethod , (aunque no ampliamente probado):

 #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 

¿No funciona tener un NSBlockOperation (iOS SDK +5)? Este código usa ARC y es una simplificación de una aplicación con la que estoy probando esto (parece funcionar, al menos aparentemente, no estoy seguro si estoy perdiendo memoria).

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

Por supuesto, no estoy seguro de cuán bueno es esto para el uso real. Debes mantener viva una referencia a NSBlockOperation o creo que ARC la eliminará.