Posso passare un blocco come @selector con Objective-C?

È ansible passare un blocco Objective-C per l’argomento @selector in un UIButton ? cioè, c’è un modo per ottenere il seguente lavoro?

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

Grazie

Sì, ma dovresti usare una categoria.

Qualcosa di simile a:

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

L’implementazione sarebbe un po ‘più complicata:

 #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 

Qualche spiegazione:

  1. Stiamo usando una class personalizzata “solo interno” chiamata DDBlockActionWrapper . Questa è una class semplice che ha una proprietà di blocco (il blocco che vogliamo richiamare) e un metodo che richiama semplicemente quel blocco.
  2. La categoria UIControl semplicemente l’istanza di uno di questi wrapper, gli dà il blocco da invocare, e poi si dice di usare quel wrapper e il suo invokeBlock: metodo come target e action (normalmente).
  3. La categoria UIControl utilizza un object associato per memorizzare una serie di DDBlockActionWrappers , poiché UIControl non mantiene i propri obiettivi. Questo array serve a garantire che i blocchi esistano quando devono essere richiamati.
  4. Dobbiamo assicurarci che DDBlockActionWrappers ripulito quando l’object viene distrutto, quindi stiamo facendo un brutto -[UIControl dealloc] di swizzling -[UIControl dealloc] con uno nuovo che rimuove l’object associato, e quindi invoca il codice dealloc originale. Ingannevole, difficile. In realtà, gli oggetti associati vengono ripuliti automaticamente durante la deallocazione .

Infine, questo codice è stato digitato nel browser e non è stato compilato. Probabilmente ci sono alcune cose sbagliate. Il tuo chilometraggio può variare.

I blocchi sono oggetti. Passa il tuo blocco come argomento di target , con @selector(invoke) come argomento di action , come questo:

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

No, selettori e blocchi non sono tipi compatibili in Objective-C (in realtà sono cose molto diverse). Dovrai scrivere il tuo metodo e passare invece il suo selettore.

È ansible passare un blocco Objective-C per l’argomento @selector in un UIButton?

Prendendo in considerazione tutte le risposte già fornite, la risposta è Sì ma è necessario un minimo di lavoro per impostare alcune categorie.

Raccomando di usare NSInvocation perché puoi fare molto con questo come con i timer, archiviato come object e invocato … ecc …

Ecco cosa ho fatto, ma nota che sto usando ARC.

La prima è una categoria semplice su 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 

La prossima è una categoria su NSInvocation da memorizzare in un blocco:

.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 

Ecco come usarlo:

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

Puoi fare molto con l’invocazione e i metodi standard Objective-C. Ad esempio, è ansible utilizzare NSInvocationOperation (initWithInvocation :), NSTimer (scheduledTimerWithTimeInterval: invocation: repeates 🙂

Il punto è trasformare il tuo blocco in un NSInvocation è più versatile e può essere usato come tale:

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

Ancora una volta questo è solo un suggerimento.

Non così semplice, sfortunatamente.

In teoria, sarebbe ansible definire una funzione che aggiunge dynamicmente un metodo alla class di target , che quel metodo esegua il contenuto di un blocco e restituisca un selettore come richiesto dall’argomento action . Questa funzione potrebbe utilizzare la tecnica utilizzata da MABlockClosure , che, nel caso di iOS, dipende da un’implementazione personalizzata di libffi, che è ancora sperimentale.

È meglio implementare l’azione come metodo.

La libreria BlocksKit su Github (disponibile anche come CocoaPod) ha questa funzione integrata.

Dai un’occhiata al file di intestazione per UIControl + BlocksKit.h. Hanno implementato l’idea di Dave DeLong in modo da non doverlo fare. Qualche documentazione è qui .

Qualcuno mi dirà perché questo è sbagliato, forse, o con un po ‘di fortuna, forse no, quindi imparerò qualcosa o sarò utile.

L’ho appena buttato insieme. È molto semplice, solo un involucro sottile con un po ‘di fusione. Una parola di avvertimento, presuppone che il blocco che stai invocando abbia la firma corretta per abbinare il selettore che usi (cioè il numero di argomenti e tipi).

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

Non c’è davvero nulla di magico in corso. Solo un sacco di downcasting per void * e typecasting a una firma di blocco utilizzabile prima di invocare il metodo. Ovviamente (proprio come con performSelector: e il metodo associato, le possibili combinazioni di input sono finite, ma estendibili se si modifica il codice.

Usato in questo modo:

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

Emette:

2011-01-03 16: 11: 16.020 BlockInvocation [37096: a0f] Il blocco è stato richiamato con str = Test

Utilizzato in uno scenario di azione di destinazione, devi solo fare qualcosa del genere:

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

Poiché l’objective in un sistema di azione di destinazione non viene mantenuto, è necessario assicurarsi che l’object di invocazione viva per il controllo stesso.

Sono interessato a sentire qualsiasi cosa da qualcuno più esperto di me.

Avevo bisogno di avere un’azione associata a un UIButton all’interno di UITableViewCell. Volevo evitare di usare i tag per rintracciare ogni pulsante in ogni cella diversa. Ho pensato che il modo più diretto per raggiungere questo objective era quello di associare un blocco “azione” al pulsante in questo modo:

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

La mia implementazione è un po ‘più semplificata, grazie a @bbum per aver menzionato imp_implementationWithBlock e class_addMethod , (sebbene non ampiamente testati):

 #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, "[email protected]:"); 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 

Non funziona con NSBlockOperation (iOS SDK +5). Questo codice utilizza ARC ed è una semplificazione di un’App con cui sto testando questo (sembra funzionare, almeno apparentemente, non so se sto perdendo 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]; } 

Certo, non sono sicuro di quanto sia buono per l’uso reale. È necessario mantenere vivo un riferimento a NSBlockOperation o penso che ARC lo uccida.