Да, но вам нужно будет использовать категорию.
Что-то типа:
@interface UIControl (DDBlockActions)
- (void) addEventHandler:(void(^)(void))handler
forControlEvents:(UIControlEvents)controlEvents;
@end
Реализация будет немного сложнее:
#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
Некоторое объяснение:
- Мы используем специальный класс «только для внутреннего использования»
DDBlockActionWrapper
. Это простой класс, имеющий свойство блока (блок, который мы хотим вызвать) и метод, который просто вызывает этот блок.
UIControl
Категория просто конкретизирует один из этих оберток, придает ей блок , который будет вызываться, а затем говорит самому использовать эту обертку и его invokeBlock:
метод в качестве цели и действия (как обычно).
UIControl
Категория использует связанный с ним объект для хранения массива DDBlockActionWrappers
, поскольку UIControl
не сохраняет свои цели. Этот массив должен гарантировать, что блоки существуют, когда они должны быть вызваны.
Мы должны убедиться, что DDBlockActionWrappers
объект очищается, когда объект уничтожается, поэтому мы делаем неприятный прием, заменяя -[UIControl dealloc]
новый объект, который удаляет связанный объект, а затем вызывает исходный dealloc
код. Хитро, хитро. Фактически связанные объекты очищаются автоматически во время освобождения .
Наконец, этот код был набран в браузере и не компилировался. Возможно, с этим что-то не так. Ваш пробег может отличаться.
objc_implementationWithBlock()
иclass_addMethod()
для решения этой проблемы несколько более эффективным способом, чем использование связанных объектов (что подразумевает поиск хэша, который не так эффективен, как поиск метода). Возможно несущественная разница в производительности, но это альтернатива.imp_implementationWithBlock
?objc_implementationWithBlock()
. :)UITableViewCell
приведет к дублированию желаемых целей-действий, поскольку каждая новая цель является новым экземпляром, а предыдущие не очищаются для тех же событий. Вы должны сначала очистить мишениfor (id t in self.allTargets) { [self removeTarget:t action:@selector(invokeBlock:) forControlEvents:controlEvents]; } [self addTarget:target action:@selector(invokeBlock:) forControlEvents:controlEvents];
Блоки - это объекты. Пройдите свой блок в качестве
target
аргумента, с@selector(invoke)
какaction
аргумент, как это:id block = [^{NSLog(@"Hello, world");} copy];// Don't forget to -release. [button addTarget:block action:@selector(invoke) forControlEvents:UIControlEventTouchUpInside];
источник
invoke
метод на блочных объектах не является общедоступным и не предназначено для использования в этой моде.nil
вместо@selector(invoke)
.Нет, селекторы и блоки не являются совместимыми типами в Objective-C (на самом деле, это разные вещи). Вам придется написать свой собственный метод и вместо этого передать его селектор.
источник
Принимая во внимание все уже предоставленные ответы, ответ - да, но для настройки некоторых категорий необходимо немного поработать.
Я рекомендую использовать NSInvocation, потому что вы можете многое сделать с этим, например, с таймерами, хранящимися как объект и вызываемыми ... и т. Д.
Вот что я сделал, но обратите внимание, что я использую ARC.
Сначала простая категория на NSObject:
.час
@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
Далее идет категория NSInvocation для хранения в блоке:
.час
@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
Вот как им пользоваться:
NSInvocation *invocation = [NSInvocation invocationWithTarget:self block:^(id target) { NSLog(@"TEST"); }]; [invocation invoke];
Вы можете многое сделать с помощью вызова и стандартных методов Objective-C. Например, вы можете использовать NSInvocationOperation (initWithInvocation :), NSTimer (scheduleTimerWithTimeInterval: invocation: repeatates :)
Дело в том, что превращение вашего блока в NSInvocation более универсально и может использоваться как таковое:
NSInvocation *invocation = [NSInvocation invocationWithTarget:self block:^(id target) { NSLog(@"My Block code here"); }]; [button addTarget:invocation action:@selector(invoke) forControlEvents:UIControlEventTouchUpInside];
Опять же, это всего лишь одно предложение.
источник
К сожалению, не все так просто.
Теоретически можно было бы определить функцию, которая динамически добавляет метод к классу
target
, чтобы этот метод выполнял содержимое блока и возвращал селектор, если это необходимоaction
аргументу. Эта функция может использовать технику, используемую MABlockClosure , которая в случае iOS зависит от специальной реализации libffi, которая все еще является экспериментальной.Лучше реализовать действие как метод.
источник
Библиотека BlocksKit на Github (также доступная как CocoaPod) имеет эту встроенную функцию.
Взгляните на файл заголовка для UIControl + BlocksKit.h. Они реализовали идею Дэйва Делонга, так что вам не нужно. Некоторая документация здесь .
источник
Кто-нибудь скажет мне, почему это неправильно, может быть, или, если повезет, может нет, так что я либо выучу что-нибудь, либо буду полезен.
Я просто скинул это вместе. Это действительно просто, просто тонкая обертка с небольшим количеством литья. Слово предупреждения: предполагается, что вызываемый вами блок имеет правильную сигнатуру, соответствующую используемому селектору (т.е. количеству аргументов и типов).
// // 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
А также
// // 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
На самом деле ничего волшебного не происходит. Просто множество преобразований
void *
и приведений к пригодной для использования подписи блока перед вызовом метода. Очевидно (какperformSelector:
и связанный с ним метод, возможные комбинации входных данных конечны, но могут быть расширены, если вы измените код.Используется так:
BlockInvocation *invocation = [BlockInvocation invocationWithBlock:^(NSString *str) { NSLog(@"Block was invoked with str = %@", str); }]; [invocation performWithObject:@"Test"];
Он выводит:
При использовании в сценарии целевого действия вам просто нужно сделать что-то вроде этого:
BlockInvocation *invocation = [[BlockInvocation alloc] initWithBlock:^(id sender) { NSLog(@"Button with title %@ was clicked", [(NSButton *)sender title]); }]; [myButton setTarget:invocation]; [myButton setAction:@selector(performWithObject:)];
Поскольку цель в системе целевых действий не сохраняется, вам нужно будет гарантировать, что вызывающий объект живет столько же, сколько и сам элемент управления.
Мне интересно услышать что-нибудь от кого-нибудь более опытного, чем я.
источник
invocation
он никогда не выпускаетсяМне нужно было связать действие с UIButton в UITableViewCell. Я хотел избежать использования тегов для отслеживания каждой кнопки в каждой отдельной ячейке. Я думал, что самый простой способ добиться этого - связать блок «действие» с кнопкой следующим образом:
[cell.trashButton addTarget:self withActionBlock:^{ NSLog(@"Will remove item #%d from cart!", indexPath.row); ... } forControlEvent:UIControlEventTouchUpInside];
Моя реализация немного упрощена благодаря @bbum за упоминание
imp_implementationWithBlock
иclass_addMethod
(хотя и не тщательно протестирован):#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
источник
Разве не работает NSBlockOperation (iOS SDK +5). Этот код использует ARC, и это упрощение приложения, с которым я его тестирую (похоже, работает, по крайней мере, по-видимому, не уверен, не происходит ли утечка памяти).
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]; }
Конечно, я не уверен, насколько это хорошо для реального использования. Вам нужно сохранить ссылку на NSBlockOperation, иначе я думаю, что ARC убьет ее.
источник