我可以通过Objective-C作为@selector传递一个块吗?
是否有可能在UIButton
传递@selector
参数的Objective-C块? 即,有什么办法让以下工作?
[closeOverlayButton addTarget:self action:^ {[anotherIvarLocalToThisMethod removeFromSuperview];} forControlEvents:UIControlEventTouchUpInside];
谢谢
是的,但你必须使用一个类别。
就像是:
@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
代码。棘手,棘手。实际上, 关联对象在释放期间被自动清理 。
最后,这个代码是在浏览器中input的,并没有被编译。 这可能有些问题。 你的旅费可能会改变。
块是对象。 以@selector(invoke)
作为action
parameter passing你的块作为target
参数,就像这样:
id block = [^{NSLog(@"Hello, world");} copy];// Don't forget to -release. [button addTarget:block action:@selector(invoke) forControlEvents:UIControlEventTouchUpInside];
不,在Objective-C中,select器和块是不兼容的types(事实上,它们是非常不同的东西)。 你将不得不编写你自己的方法,而是传递它的select器。
是否有可能在UIButton中传递@selector参数的Objective-C块?
考虑到所有已经提供的答案,答案是肯定的,但需要一些工作来设置一些类别。
我build议使用NSInvocation,因为你可以做很多事情,例如定时器,存储为对象和调用…等等。
这是我做的,但注意我正在使用ARC。
首先是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
接下来是NSInvocation的一个类别存储在一个块中:
。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
以下是如何使用它:
NSInvocation *invocation = [NSInvocation invocationWithTarget:self block:^(id target) { NSLog(@"TEST"); }]; [invocation invoke];
您可以使用调用和标准的Objective-C方法做很多事情。 例如,你可以使用NSInvocationOperation(initWithInvocation :),NSTimer(scheduledTimerWithTimeInterval:invocation:repeates 🙂
重点是把你的块变成一个NSInvocation是更通用的,可以这样使用:
NSInvocation *invocation = [NSInvocation invocationWithTarget:self block:^(id target) { NSLog(@"My Block code here"); }]; [button addTarget:invocation action:@selector(invoke) forControlEvents:UIControlEventTouchUpInside];
这又是一个build议。
不幸的是,不那么简单。
从理论上讲,可以定义一个dynamic地将方法添加到target
类的函数,让该方法执行块的内容,并根据action
参数的需要返回select器。 这个函数可以使用MABlockClosure使用的技术,在iOS的情况下,它依赖于libffi的自定义实现,这仍然是实验性的。
作为一种方法,你最好实施这个行动。
Github上的库BlocksKit (也可以作为CocoaPod)具有内置的这个特性。
看看UIControl + BlocksKit.h的头文件。 他们已经实现了Dave DeLong的想法,所以你不必这样做。 一些文档在这里 。
有人会告诉我为什么这是错的,也许,或者有什么运气,也许不是,所以我要么学到一些东西,要么会有所帮助。
我只是把它扔在一起。 这是非常基本的,只是一个铸造的一个薄包装。 一个警告的话,它假定你正在调用的块有正确的签名来匹配你使用的select器(即参数和types的数量)。
// // 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 *
和types转换为可用的块签名。 显然(就像使用performSelector:
和相关的方法一样,input的可能组合是有限的,但是如果你修改了代码,则可扩展。
像这样使用:
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]使用str = Test
在目标行动场景中使用,你只需要做这样的事情:
BlockInvocation *invocation = [[BlockInvocation alloc] initWithBlock:^(id sender) { NSLog(@"Button with title %@ was clicked", [(NSButton *)sender title]); }]; [myButton setTarget:invocation]; [myButton setAction:@selector(performWithObject:)];
由于目标操作系统中的目标不被保留,因此只要控件本身具有,就需要确保调用对象的存在。
我有兴趣听到有人比我更专家。
我需要在UITableViewCell中有一个与UIButton相关的操作。 我想避免使用标签来追踪每个不同单元格中的每个button。 我认为实现这个最直接的方法是将一个块的“操作”关联到button上,如下所示:
[cell.trashButton addTarget:self withActionBlock:^{ NSLog(@"Will remove item #%d from cart!", indexPath.row); ... } forControlEvent:UIControlEventTouchUpInside];
我的实现更简单一点,感谢@ bbum提到imp_implementationWithBlock
和class_addMethod
(尽pipe没有经过广泛的testing):
#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,它是一个简化的应用程序,我正在testing这个(似乎工作,至less显然,不知道如果我正在泄漏内存)。
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会杀死它。