objective-c - framework - cocoa touch español
¿Puedo pasar un bloque como @selector con Objective-C? (9)
¿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 <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
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.
¿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
¿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á.
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 <Cocoa/Cocoa.h>
@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 objetivo, 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 objetivo en un sistema de acción objetivo 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.
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];
Necesitaba tener una acción asociada a un UIButton dentro de una UITableViewCell. Quería evitar el uso de etiquetas 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 <objc/runtime.h>
@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 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.
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.
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 <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
Alguna explicación:
- 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. - 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étodoinvokeBlock:
como objetivo y acción (como es normal). - La categoría
UIControl
usa un objeto asociado para almacenar una matriz deDDBlockActionWrappers
, porqueUIControl
no conserva sus objetivos. Esta matriz es para garantizar que los bloques existan cuando se supone que deben invocarse. -
Tenemos que asegurarnos de que losDDBlockActionWrappers
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ódigodealloc
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 compilado. Probablemente hay algunas cosas mal con eso. Su experiencia puede ser diferente.