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 usando una clase personalizada "solo interna" llamada
DDBlockActionWrapper
. Esta es una clase simple que tiene una propiedad de bloque (el bloque que queremos que se invoque) y un método que simplemente invoca ese bloque.
- La
UIControl
categoría simplemente crea una instancia de uno de estos envoltorios, le da el bloque que se invocará y luego se dice a sí misma que use ese envoltorio y su invokeBlock:
método como destino y acción (como es normal).
- La
UIControl
categoría usa un objeto asociado para almacenar una matriz DDBlockActionWrappers
, porque UIControl
no retiene sus objetivos. Esta matriz es para garantizar que los bloques existan cuando se supone que deben invocarse.
Tenemos que asegurarnos de que DDBlockActionWrappers
se limpien cuando se destruye el objeto, por lo que estamos haciendo un truco desagradable de swizzling -[UIControl dealloc]
con uno nuevo que elimina el objeto asociado y luego invoca el dealloc
código original . Difícil, complicado. En realidad, los objetos asociados se limpian automáticamente durante la desasignación .
Finalmente, este código se escribió en el navegador y no se ha compilado. Probablemente haya algunas cosas mal en él. Su experiencia puede ser diferente.
objc_implementationWithBlock()
yclass_addMethod()
para resolver este problema de una manera un poco más eficiente que usando objetos asociados (lo que implica una búsqueda de hash que no es tan eficiente como la búsqueda de métodos). Probablemente una diferencia de rendimiento irrelevante, pero es una alternativa.imp_implementationWithBlock
?objc_implementationWithBlock()
. :)UITableViewCell
dará como resultado la duplicación de objetivos-acciones deseados, ya que cada nuevo objetivo es una nueva instancia y los anteriores no se limpian para los mismos eventos. Primero tienes que limpiar los objetivosfor (id t in self.allTargets) { [self removeTarget:t action:@selector(invokeBlock:) forControlEvents:controlEvents]; } [self addTarget:target action:@selector(invokeBlock:) forControlEvents:controlEvents];
Los bloques son objetos. Pase su bloque como
target
argumento, con@selector(invoke)
comoaction
argumento, así:fuente
invoke
método de los objetos Block no es público y no está destinado a utilizarse de esta manera.nil
lugar de@selector(invoke)
.No, los selectores y bloques no son tipos compatibles en Objective-C (de hecho, son cosas muy diferentes). Tendrá que escribir su propio método y pasar su selector en su lugar.
fuente
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 puede hacer mucho con esto, como con temporizadores, almacenados como un objeto e invocados ... etc ...
Esto es lo que hice, pero tenga en cuenta que estoy usando ARC.
Primero hay una categoría simple en NSObject:
.h
.metro
La siguiente es una categoría en NSInvocation para almacenar en un bloque:
.h
.metro
Así es como se usa:
Puede hacer mucho con la invocación y los métodos estándar de Objective-C. Por ejemplo, puede utilizar NSInvocationOperation (initWithInvocation :), NSTimer (scheduleTimerWithTimeInterval: invocation: repeates :)
El punto es convertir su bloque en una NSInvocation es más versátil y se puede usar como tal:
Nuevamente, esta es solo una sugerencia.
fuente
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 elaction
argumento. 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 aún es experimental.Es mejor implementar la acción como método.
fuente
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 usted no tenga que hacerlo. Alguna documentación está aquí .
fuente
Alguien me va a decir por qué esto está mal, tal vez, o con suerte, tal vez no, así que aprenderé algo o seré útil.
Acabo de lanzar esto junto. Es realmente básico, solo una envoltura delgada con un poco de yeso. Una advertencia, asume que el bloque que estás invocando tiene la firma correcta para coincidir con el selector que usas (es decir, número de argumentos y tipos).
Y
Realmente no está sucediendo nada mágico. Solo un montón de downcasting
void *
y encasillado a una firma de bloque utilizable antes de invocar el método. Obviamente (al igual que conperformSelector:
método asociado, las posibles combinaciones de entradas son finitas, pero ampliables si modificas el código.Usado así:
Produce:
Usado en un escenario de acción objetivo, solo necesita hacer algo como esto:
Dado que el destino en un sistema de acción de destino no se retiene, deberá asegurarse de que el objeto de invocación dure tanto tiempo como el control.
Me interesa saber algo de alguien más experto que yo.
fuente
invocation
nunca se publicaNecesitaba tener una acción asociada a un UIButton dentro de un 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 bloque al botón así:
Mi implementación es un poco más simplificada, gracias a @bbum por mencionar
imp_implementationWithBlock
yclass_addMethod
, (aunque no se ha probado exhaustivamente):fuente
¿No funciona tener una 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 de si estoy perdiendo memoria).
Por supuesto, no estoy seguro de qué tan bueno es esto para un uso real. Necesitas mantener viva una referencia a NSBlockOperation o creo que ARC lo matará.
fuente