Can I pass a block as a @selector with Objective-C?
是否可以在
1 2 3 | [closeOverlayButton addTarget:self action:^ {[anotherIvarLocalToThisMethod removeFromSuperview];} forControlEvents:UIControlEventTouchUpInside]; |
谢谢
是的,但是您必须使用类别。
类似的东西:
1 2 3 4 5 6 | @interface UIControl (DDBlockActions) - (void) addEventHandler:(void(^)(void))handler forControlEvents:(UIControlEvents)controlEvents; @end |
实现会有些棘手:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | #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 |
一些解释:
最后,此代码是在浏览器中键入的,尚未编译。可能有些问题。您的里程可能会有所不同。
块是对象。将块作为
1 2 3 4 5 | id block = [^{NSLog(@"Hello, world");} copy];// Don't forget to -release. [button addTarget:block action:@selector(invoke) forControlEvents:UIControlEventTouchUpInside]; |
不,选择器和块在Objective-C中不是兼容类型(实际上,它们是完全不同的东西)。您必须编写自己的方法,然后传递其选择器。
Is it possible to pass an Objective-C block for the @selector argument in a UIButton?
在所有已经提供的答案中,答案是肯定的,但是需要一些工作来设置某些类别。
我建议使用NSInvocation,因为您可以做很多事情,例如使用计时器,将其存储为对象并调用...等...
这是我所做的,但请注意,我正在使用ARC。
首先是NSObject上的一个简单类别:
.h
1 2 3 4 5 6 | @interface NSObject (CategoryNSObject) - (void) associateValue:(id)value withKey:(NSString *)aKey; - (id) associatedValueForKey:(NSString *)aKey; @end |
.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #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上的类别:
.h
1 2 3 4 5 6 7 | @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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | #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 |
这里是使用方法:
1 2 3 4 | NSInvocation *invocation = [NSInvocation invocationWithTarget:self block:^(id target) { NSLog(@"TEST"); }]; [invocation invoke]; |
您可以使用调用和标准的Objective-C方法做很多事情。例如,您可以使用NSInvocationOperation(initWithInvocation :),NSTimer(scheduledTimerWithTimeInterval:invocation:repeates:)
要点是将您的代码块转换为NSInvocation更为通用,可以这样使用:
1 2 3 4 5 6 | NSInvocation *invocation = [NSInvocation invocationWithTarget:self block:^(id target) { NSLog(@"My Block code here"); }]; [button addTarget:invocation action:@selector(invoke) forControlEvents:UIControlEventTouchUpInside]; |
同样,这只是一个建议。
不幸的是,事情并非如此简单。
从理论上讲,可以定义一个函数,该函数可以动态地向
您最好将动作作为方法来实现。
Github上的BlocksKit库(也可以作为CocoaPod获得)具有内置的此功能。
看看UIControl BlocksKit.h的头文件。他们已经实现了Dave DeLong的想法,所以您不必这样做。一些文档在这里。
我需要有一个与UITableViewCell中的UIButton关联的动作。我想避免使用标签来跟踪每个不同单元格中的每个按钮。我认为实现这一目标的最直接方法是将一个"动作"块与该按钮相关联,如下所示:
1 2 3 4 5 | [cell.trashButton addTarget:self withActionBlock:^{ NSLog(@"Will remove item #%d from cart!", indexPath.row); ... } forControlEvent:UIControlEventTouchUpInside]; |
由于@bbum提到了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #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 |
有人会告诉我为什么这可能是错的,也许是运气,也许不是,所以我会学点东西,或者会有所帮助。
我只是把这些放在一起了。这真的很基础,只是带有一些浇铸的薄package。提示一下,它假设您正在调用的块具有正确的签名以匹配您使用的选择器(即参数和类型的数量)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // // 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 |
和
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | // // 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 |
真的没有什么神奇的事情发生。在调用该方法之前,只需要大量向下转换到
像这样使用:
1 2 3 4 | BlockInvocation *invocation = [BlockInvocation invocationWithBlock:^(NSString *str) { NSLog(@"Block was invoked with str = %@", str); }]; [invocation performWithObject:@"Test"]; |
输出:
2011-01-03 16:11:16.020 BlockInvocation[37096:a0f] Block was invoked with str = Test
在目标动作场景中使用,您只需要执行以下操作:
1 2 3 4 5 | BlockInvocation *invocation = [[BlockInvocation alloc] initWithBlock:^(id sender) { NSLog(@"Button with title %@ was clicked", [(NSButton *)sender title]); }]; [myButton setTarget:invocation]; [myButton setAction:@selector(performWithObject:)]; |
由于未保留目标操作系统中的目标,因此您需要确保调用对象的生存时间与控件本身一样长。
我有兴趣听到比我更专业的人的任何事情。
拥有NSBlockOperation(iOS SDK 5)不可行。这段代码使用了ARC,它是我正在对其进行测试的App的简化版(似乎可以正常工作,至少显然地,不确定我是否在泄漏内存)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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将杀死它。