Tak, ale musiałbyś użyć kategorii.
Coś jak:
@interface UIControl (DDBlockActions)
- (void) addEventHandler:(void(^)(void))handler
forControlEvents:(UIControlEvents)controlEvents;
@end
Wdrożenie byłoby nieco trudniejsze:
#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
Kilka wyjaśnień:
- Używamy niestandardowej klasy „tylko do użytku wewnętrznego” o nazwie
DDBlockActionWrapper
. Jest to prosta klasa, która ma właściwość block (blok, który chcemy wywołać) oraz metodę, która po prostu wywołuje ten blok.
UIControl
Kategoria prostu instancję jednego z tych opakowań, daje to blok się powoływać, a następnie informuje się do korzystania z tego opakowania i jego invokeBlock:
metodę jako cel i działania (jak zwykle).
UIControl
Kategoria wykorzystuje wiązany obiekt do przechowywania tablicę DDBlockActionWrappers
, ponieważ UIControl
nie zachowuje swoje cele. Ta tablica ma zapewnić, że bloki istnieją w momencie, gdy mają zostać wywołane.
Musimy upewnić się, że DDBlockActionWrappers
zostanie wyczyszczony, gdy obiekt zostanie zniszczony, więc robimy nieprzyjemny hack polegający na wymieszaniu -[UIControl dealloc]
z nowym, który usuwa powiązany obiekt, a następnie wywołuje oryginalny dealloc
kod. Podstępne, podstępne. W rzeczywistości powiązane obiekty są czyszczone automatycznie podczas cofania przydziału .
Ostatecznie ten kod został wpisany w przeglądarce i nie został skompilowany. Prawdopodobnie jest z nim nie tak. Twój przebieg może się różnić.
objc_implementationWithBlock()
iclass_addMethod()
rozwiązać ten problem w nieco bardziej efektywny sposób niż używanie skojarzonych obiektów (co implikuje wyszukiwanie skrótu, które nie jest tak wydajne jak wyszukiwanie metod). Prawdopodobnie nieistotna różnica w wydajności, ale jest to alternatywa.imp_implementationWithBlock
?objc_implementationWithBlock()
. :)UITableViewCell
spowoduje powielenie żądanych działań-celów, ponieważ każdy nowy cel jest nową instancją, a poprzednie nie są czyszczone dla tych samych zdarzeń. Najpierw musisz wyczyścić celefor (id t in self.allTargets) { [self removeTarget:t action:@selector(invokeBlock:) forControlEvents:controlEvents]; } [self addTarget:target action:@selector(invokeBlock:) forControlEvents:controlEvents];
Bloki to obiekty. Przekaż swój blok jako
target
argument,@selector(invoke)
jakoaction
argument, na przykład:id block = [^{NSLog(@"Hello, world");} copy];// Don't forget to -release. [button addTarget:block action:@selector(invoke) forControlEvents:UIControlEventTouchUpInside];
źródło
invoke
metoda na obiektach blok nie jest publiczny i nie są przeznaczone do wykorzystania w ten sposób.nil
zamiast@selector(invoke)
.Nie, selektory i bloki nie są kompatybilnymi typami w Objective-C (w rzeczywistości są to bardzo różne rzeczy). Będziesz musiał napisać własną metodę i zamiast tego przekazać jej selektor.
źródło
Biorąc pod uwagę wszystkie udzielone już odpowiedzi, odpowiedź brzmi: tak, ale konfiguracja niektórych kategorii wymaga odrobiny pracy.
Polecam używanie NSInvocation, ponieważ możesz wiele z tym zrobić, na przykład z licznikami czasu, przechowywanymi jako obiekt i wywoływanymi ... itd.
Oto co zrobiłem, ale zauważ, że używam ARC.
Pierwsza to prosta kategoria na NSObject:
.h
@interface NSObject (CategoryNSObject) - (void) associateValue:(id)value withKey:(NSString *)aKey; - (id) associatedValueForKey:(NSString *)aKey; @end
.m
#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
Dalej jest kategoria w NSInvocation do przechowywania w bloku:
.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
Oto jak z niego korzystać:
NSInvocation *invocation = [NSInvocation invocationWithTarget:self block:^(id target) { NSLog(@"TEST"); }]; [invocation invoke];
Możesz wiele zrobić za pomocą wywołania i standardowych metod Objective-C. Na przykład możesz użyć NSInvocationOperation (initWithInvocation :), NSTimer (scheduleTimerWithTimeInterval: invocation: repeates :)
Chodzi o to, że przekształcenie twojego bloku w NSInvocation jest bardziej wszechstronne i może być używane jako takie:
NSInvocation *invocation = [NSInvocation invocationWithTarget:self block:^(id target) { NSLog(@"My Block code here"); }]; [button addTarget:invocation action:@selector(invoke) forControlEvents:UIControlEventTouchUpInside];
To tylko jedna sugestia.
źródło
Niestety nie jest to takie proste.
W teorii byłoby możliwe zdefiniowanie funkcji, która dynamicznie dodaje metodę do klasy of
target
, powoduje wykonanie przez tę metodę zawartości bloku i zwrócenie selektora zgodnie z wymaganiamiaction
argumentu. Ta funkcja może wykorzystywać technikę używaną przez MABlockClosure , która w przypadku iOS zależy od niestandardowej implementacji libffi, która wciąż jest eksperymentalna.Lepiej zaimplementuj akcję jako metodę.
źródło
Biblioteka BlocksKit na Github (dostępna również jako CocoaPod) ma wbudowaną tę funkcję.
Spójrz na plik nagłówkowy UIControl + BlocksKit.h. Wdrożyli pomysł Dave'a DeLonga, więc nie musisz. Część dokumentacji jest tutaj .
źródło
Ktoś mi powie, dlaczego to jest złe, może lub przy odrobinie szczęścia, może nie, więc albo się czegoś nauczę, albo będę pomocny.
Po prostu rzuciłem to razem. To naprawdę proste, po prostu cienkie opakowanie z odrobiną odlewu. Słowo ostrzeżenia, zakłada, że wywoływany blok ma poprawną sygnaturę pasującą do selektora, którego używasz (tj. Liczbę argumentów i typów).
// // 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
I
// // 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
Naprawdę nie dzieje się nic magicznego. Tylko dużo downcastingu
void *
i typecastingu do użytecznego podpisu blokowego przed wywołaniem metody. Oczywiście (podobnie jak w przypadkuperformSelector:
metody i powiązanej metody, możliwe kombinacje danych wejściowych są ograniczone, ale można je rozszerzyć, jeśli zmodyfikujesz kod.Używane w ten sposób:
BlockInvocation *invocation = [BlockInvocation invocationWithBlock:^(NSString *str) { NSLog(@"Block was invoked with str = %@", str); }]; [invocation performWithObject:@"Test"];
Wyprowadza:
Używany w scenariuszu typu cel-akcja, wystarczy zrobić coś takiego:
BlockInvocation *invocation = [[BlockInvocation alloc] initWithBlock:^(id sender) { NSLog(@"Button with title %@ was clicked", [(NSButton *)sender title]); }]; [myButton setTarget:invocation]; [myButton setAction:@selector(performWithObject:)];
Ponieważ cel w systemie cel-akcja nie jest zachowany, będziesz musiał upewnić się, że obiekt wywołania będzie żył tak długo, jak długo to robi.
Chciałbym usłyszeć coś od kogoś bardziej eksperta niż ja.
źródło
invocation
nigdy nie został wydanyMusiałem mieć akcję skojarzoną z UIButton w ramach UITableViewCell. Chciałem uniknąć używania tagów do śledzenia każdego przycisku w każdej innej komórce. Pomyślałem, że najbardziej bezpośrednim sposobem osiągnięcia tego jest skojarzenie bloku „akcja” z przyciskiem w następujący sposób:
[cell.trashButton addTarget:self withActionBlock:^{ NSLog(@"Will remove item #%d from cart!", indexPath.row); ... } forControlEvent:UIControlEventTouchUpInside];
Moja implementacja jest nieco bardziej uproszczona, dzięki @bbum za wzmiankę
imp_implementationWithBlock
iclass_addMethod
(choć nie dogłębnie przetestowane):#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
źródło
Nie działa, aby mieć NSBlockOperation (iOS SDK +5). Ten kod używa ARC i jest uproszczeniem aplikacji, w której to testuję (wydaje się działać, przynajmniej pozornie, nie jestem pewien, czy wyciekam pamięć).
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]; }
Oczywiście nie jestem pewien, jak dobre jest to w prawdziwym użyciu. Musisz zachować przy życiu odniesienie do NSBlockOperation albo myślę, że ARC go zabije.
źródło