Is it possible to pass an Objective-C block for the @selector
argument in a UIButton
? i.e., Is there any way to get the following to work?
是否可以在UIButton中传递@selector参数的Objective-C块?即。有什么方法可以让下面的人工作吗?
[closeOverlayButton addTarget:self
action:^ {[anotherIvarLocalToThisMethod removeFromSuperview];}
forControlEvents:UIControlEventTouchUpInside];
Thanks
谢谢
9 个解决方案
#1
69
Yes, but you'd have to use a category.
是的,但是你必须使用分类。
Something like:
喜欢的东西:
@interface UIControl (DDBlockActions)
- (void) addEventHandler:(void(^)(void))handler
forControlEvents:(UIControlEvents)controlEvents;
@end
The implementation would be a bit trickier:
这个实现会有点棘手:
#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
Some explanation:
这里做一些解释:
- We're using a custom "internal only" class called
DDBlockActionWrapper
. This is a simple class that has a block property (the block we want to get invoked), and a method that simply invokes that block. - 我们使用的是自定义的“内部唯一”类,称为DDBlockActionWrapper。这是一个具有块属性(我们希望被调用的块)的简单类,以及一个简单地调用该块的方法。
- The
UIControl
category simply instantiates one of these wrappers, gives it the block to be invoked, and then tells itself to use that wrapper and itsinvokeBlock:
method as the target and action (as normal). - UIControl类别简单地实例化了其中一个包装器,给它一个被调用的块,然后告诉自己使用这个包装器和它的invokeBlock:方法作为目标和操作(正常)。
- The
UIControl
category uses an associated object to store an array ofDDBlockActionWrappers
, becauseUIControl
does not retain its targets. This array is to ensure that the blocks exist when they're supposed to be invoked. - UIControl类别使用一个相关联的对象来存储一组DDBlockActionWrappers,因为UIControl没有保留它的目标。这个数组是为了确保当它们被调用时,块是存在的。
-
We have to ensure that theActually, associated objects are cleaned up automatically during deallocation.DDBlockActionWrappers
get cleaned up when the object is destroyed, so we're doing a nasty hack of swizzling out-[UIControl dealloc]
with a new one that removes the associated object, and then invokes the originaldealloc
code. Tricky, tricky. - 我们必须确保当对象被销毁时,DDBlockActionWrappers会被清除,因此我们正在进行一种令人讨厌的攻击——使用一个新的删除关联对象的新方法,然后调用原始dealloc代码。困难,棘手。实际上,在deallocation期间,关联的对象会被自动清除。
Finally, this code was typed in the browser and has not been compiled. There are probably some things wrong with it. Your mileage may vary.
最后,该代码在浏览器中输入,还没有被编译。可能有一些问题。你的情况可能不同。
#2
39
Blocks are objects. Pass your block as the target
argument, with @selector(invoke)
as the action
argument, like this:
块对象。将您的块作为目标参数传递,使用@selector(invoke)作为操作参数,如下所示:
id block = [^{NSLog(@"Hello, world");} copy];// Don't forget to -release.
[button addTarget:block
action:@selector(invoke)
forControlEvents:UIControlEventTouchUpInside];
#3
17
No, selectors and blocks are not compatible types in Objective-C (in fact, they're very different things). You'll have to write your own method and pass its selector instead.
不,选择器和块不是Objective-C中的兼容类型(实际上,它们是非常不同的东西)。您将不得不编写自己的方法并通过它的选择器。
#4
7
Is it possible to pass an Objective-C block for the @selector argument in a UIButton?
是否可以在UIButton中传递@selector参数的Objective-C块?
Taking in all the already provided answers, the answer is Yes but a tiny bit of work is necessary to setup some categories.
接受所有已经提供的答案,答案是肯定的,但是需要做一点工作来设置一些类别。
I recommend using NSInvocation because you can do a lot with this such as with timers, stored as an object and invoked...etc...
我推荐使用nsinjob,因为您可以用它做很多事情,比如计时器,存储为对象,调用…等等…
Here is what I did, but note I am using ARC.
这是我做的,但是注意我使用的是ARC。
First is a simple category on NSObject:
首先是NSObject上的一个简单类别:
.h
. h
@interface NSObject (CategoryNSObject)
- (void) associateValue:(id)value withKey:(NSString *)aKey;
- (id) associatedValueForKey:(NSString *)aKey;
@end
.m
00
#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
Next is a category on NSInvocation to store in a block:
下一个是关于NSInvocation的一个分类:
.h
. 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
00
#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
Here is how to use it:
下面是如何使用它:
NSInvocation *invocation = [NSInvocation invocationWithTarget:self block:^(id target) {
NSLog(@"TEST");
}];
[invocation invoke];
You can do a lot with the invocation and the standard Objective-C Methods. For example, you can use NSInvocationOperation (initWithInvocation:), NSTimer (scheduledTimerWithTimeInterval:invocation:repeates:)
您可以使用调用和标准的Objective-C方法做很多事情。例如,您可以使用NSInvocationOperation (initWithInvocation:), NSTimer (scheduledTimerWithTimeInterval:调用:repeates:)
The point is turning your block into an NSInvocation is more versatile and can be used as such:
重点是把你的block变成一个NSInvocation,它更通用,可以这样使用:
NSInvocation *invocation = [NSInvocation invocationWithTarget:self block:^(id target) {
NSLog(@"My Block code here");
}];
[button addTarget:invocation
action:@selector(invoke)
forControlEvents:UIControlEventTouchUpInside];
Again this is just one suggestion.
这只是一个建议。
#5
5
Not as simple as that, unfortunately.
不幸的是,没有那么简单。
In theory, it would be possible to define a function that dynamically adds a method to the class of target
, have that method execute the contents of a block, and return a selector as needed by the action
argument. This function could use the technique used by MABlockClosure, which, in the case of iOS, depends on a custom implementation of libffi, which is still experimental.
理论上,可以定义一个函数,该函数将一个方法动态地添加到目标类中,让该方法执行块的内容,并根据操作参数返回一个选择器。这个函数可以使用MABlockClosure所使用的技术,在iOS的情况下,这取决于libffi的自定义实现,而libffi仍然是实验性的。
You’re better off implementing the action as a method.
你最好把行动作为一种方法来实施。
#6
4
The library BlocksKit on Github (also available as a CocoaPod) has this feature built-in.
Github上的library BlocksKit(也可用作CocoaPod)内置了这个功能。
Take a look at the header file for UIControl+BlocksKit.h. They've implemented Dave DeLong's idea so you don't have to. Some documentation is here.
看一下UIControl+BlocksKit.h的头文件。他们已经实现了戴夫·德龙的想法,所以你不必这么做。一些文档。
#7
1
Somebody is going to tell me why this is wrong, maybe, or with any luck, maybe not, so I'll either learn something, or I'll be helpful.
有人会告诉我为什么这是错的,也许,或者是运气,也许不是,所以我要么学一些东西,要么我就会帮上忙。
I just threw this together. It's really basic, just a thin-wrapper with a bit of casting. A word of warning, it assumes the block you're invoking has the correct signature to match the selector you use (i.e. number of arguments and 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
And
和
//
// 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
There's really nothing magical going on. Just lots of downcasting to void *
and typecasting to a usable block signature before invoking the method. Obviously (just like with performSelector:
and associated method, the possible combinations of inputs are finite, but extendable if you modify the code.
没有什么神奇的事情发生。在调用该方法之前,只需要大量的向下转换到void *并对可用的块签名进行类型转换。很明显(就像performSelector:和关联的方法一样,输入的可能组合是有限的,但是如果您修改了代码,则可扩展。
Used like this:
使用这样的:
BlockInvocation *invocation = [BlockInvocation invocationWithBlock:^(NSString *str) {
NSLog(@"Block was invoked with str = %@", str);
}];
[invocation performWithObject:@"Test"];
It outputs:
输出:
2011-01-03 16:11:16.020 BlockInvocation[37096:a0f] Block was invoked with str = Test
使用str = Test调用[37096:a0f]块。
Used in a target-action scenario you just need to do something like this:
在目标-动作场景中使用,您只需要做如下操作:
BlockInvocation *invocation = [[BlockInvocation alloc] initWithBlock:^(id sender) {
NSLog(@"Button with title %@ was clicked", [(NSButton *)sender title]);
}];
[myButton setTarget:invocation];
[myButton setAction:@selector(performWithObject:)];
Since the target in a target-action system is not retained, you will need to ensure the invocation object lives for as long as the control itself does.
由于目标-操作系统中的目标没有被保留,所以您需要确保调用对象的生存时间与控制本身一样长。
I'm interested to hear anything from somebody more expert than me.
我很想从比我更专业的人那里听到任何消息。
#8
1
I needed to have an action associated to a UIButton within a UITableViewCell. I wanted to avoid using tags to track down each button in every different cell. I thought the most direct way to achieve this was to associate a block "action" to the button like so:
我需要在UITableViewCell中有一个与UIButton相关联的操作。我希望避免使用标记来跟踪每个不同单元格中的每个按钮。我认为最直接的方法是将一个block“action”关联到按钮上:
[cell.trashButton addTarget:self withActionBlock:^{
NSLog(@"Will remove item #%d from cart!", indexPath.row);
...
}
forControlEvent:UIControlEventTouchUpInside];
My implementation is a bit more simplified, thanks to @bbum for mentioning imp_implementationWithBlock
and class_addMethod
, (although not extensively tested):
由于@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
#9
0
Doesn't it work to have an NSBlockOperation (iOS SDK +5). This code uses ARC and it is a simplification of an App I am testing this with (seems to work, at least apparently, not sure if I am leaking memory).
有一个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];
}
Of course, I am not sure how good this is for real usage. You need to keep a reference to the NSBlockOperation alive or I think that ARC will kill it.
当然,我不知道这对实际使用有多好。你需要保留对NSBlockOperation的引用,否则我认为它会杀死它。
#1
69
Yes, but you'd have to use a category.
是的,但是你必须使用分类。
Something like:
喜欢的东西:
@interface UIControl (DDBlockActions)
- (void) addEventHandler:(void(^)(void))handler
forControlEvents:(UIControlEvents)controlEvents;
@end
The implementation would be a bit trickier:
这个实现会有点棘手:
#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
Some explanation:
这里做一些解释:
- We're using a custom "internal only" class called
DDBlockActionWrapper
. This is a simple class that has a block property (the block we want to get invoked), and a method that simply invokes that block. - 我们使用的是自定义的“内部唯一”类,称为DDBlockActionWrapper。这是一个具有块属性(我们希望被调用的块)的简单类,以及一个简单地调用该块的方法。
- The
UIControl
category simply instantiates one of these wrappers, gives it the block to be invoked, and then tells itself to use that wrapper and itsinvokeBlock:
method as the target and action (as normal). - UIControl类别简单地实例化了其中一个包装器,给它一个被调用的块,然后告诉自己使用这个包装器和它的invokeBlock:方法作为目标和操作(正常)。
- The
UIControl
category uses an associated object to store an array ofDDBlockActionWrappers
, becauseUIControl
does not retain its targets. This array is to ensure that the blocks exist when they're supposed to be invoked. - UIControl类别使用一个相关联的对象来存储一组DDBlockActionWrappers,因为UIControl没有保留它的目标。这个数组是为了确保当它们被调用时,块是存在的。
-
We have to ensure that theActually, associated objects are cleaned up automatically during deallocation.DDBlockActionWrappers
get cleaned up when the object is destroyed, so we're doing a nasty hack of swizzling out-[UIControl dealloc]
with a new one that removes the associated object, and then invokes the originaldealloc
code. Tricky, tricky. - 我们必须确保当对象被销毁时,DDBlockActionWrappers会被清除,因此我们正在进行一种令人讨厌的攻击——使用一个新的删除关联对象的新方法,然后调用原始dealloc代码。困难,棘手。实际上,在deallocation期间,关联的对象会被自动清除。
Finally, this code was typed in the browser and has not been compiled. There are probably some things wrong with it. Your mileage may vary.
最后,该代码在浏览器中输入,还没有被编译。可能有一些问题。你的情况可能不同。
#2
39
Blocks are objects. Pass your block as the target
argument, with @selector(invoke)
as the action
argument, like this:
块对象。将您的块作为目标参数传递,使用@selector(invoke)作为操作参数,如下所示:
id block = [^{NSLog(@"Hello, world");} copy];// Don't forget to -release.
[button addTarget:block
action:@selector(invoke)
forControlEvents:UIControlEventTouchUpInside];
#3
17
No, selectors and blocks are not compatible types in Objective-C (in fact, they're very different things). You'll have to write your own method and pass its selector instead.
不,选择器和块不是Objective-C中的兼容类型(实际上,它们是非常不同的东西)。您将不得不编写自己的方法并通过它的选择器。
#4
7
Is it possible to pass an Objective-C block for the @selector argument in a UIButton?
是否可以在UIButton中传递@selector参数的Objective-C块?
Taking in all the already provided answers, the answer is Yes but a tiny bit of work is necessary to setup some categories.
接受所有已经提供的答案,答案是肯定的,但是需要做一点工作来设置一些类别。
I recommend using NSInvocation because you can do a lot with this such as with timers, stored as an object and invoked...etc...
我推荐使用nsinjob,因为您可以用它做很多事情,比如计时器,存储为对象,调用…等等…
Here is what I did, but note I am using ARC.
这是我做的,但是注意我使用的是ARC。
First is a simple category on NSObject:
首先是NSObject上的一个简单类别:
.h
. h
@interface NSObject (CategoryNSObject)
- (void) associateValue:(id)value withKey:(NSString *)aKey;
- (id) associatedValueForKey:(NSString *)aKey;
@end
.m
00
#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
Next is a category on NSInvocation to store in a block:
下一个是关于NSInvocation的一个分类:
.h
. 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
00
#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
Here is how to use it:
下面是如何使用它:
NSInvocation *invocation = [NSInvocation invocationWithTarget:self block:^(id target) {
NSLog(@"TEST");
}];
[invocation invoke];
You can do a lot with the invocation and the standard Objective-C Methods. For example, you can use NSInvocationOperation (initWithInvocation:), NSTimer (scheduledTimerWithTimeInterval:invocation:repeates:)
您可以使用调用和标准的Objective-C方法做很多事情。例如,您可以使用NSInvocationOperation (initWithInvocation:), NSTimer (scheduledTimerWithTimeInterval:调用:repeates:)
The point is turning your block into an NSInvocation is more versatile and can be used as such:
重点是把你的block变成一个NSInvocation,它更通用,可以这样使用:
NSInvocation *invocation = [NSInvocation invocationWithTarget:self block:^(id target) {
NSLog(@"My Block code here");
}];
[button addTarget:invocation
action:@selector(invoke)
forControlEvents:UIControlEventTouchUpInside];
Again this is just one suggestion.
这只是一个建议。
#5
5
Not as simple as that, unfortunately.
不幸的是,没有那么简单。
In theory, it would be possible to define a function that dynamically adds a method to the class of target
, have that method execute the contents of a block, and return a selector as needed by the action
argument. This function could use the technique used by MABlockClosure, which, in the case of iOS, depends on a custom implementation of libffi, which is still experimental.
理论上,可以定义一个函数,该函数将一个方法动态地添加到目标类中,让该方法执行块的内容,并根据操作参数返回一个选择器。这个函数可以使用MABlockClosure所使用的技术,在iOS的情况下,这取决于libffi的自定义实现,而libffi仍然是实验性的。
You’re better off implementing the action as a method.
你最好把行动作为一种方法来实施。
#6
4
The library BlocksKit on Github (also available as a CocoaPod) has this feature built-in.
Github上的library BlocksKit(也可用作CocoaPod)内置了这个功能。
Take a look at the header file for UIControl+BlocksKit.h. They've implemented Dave DeLong's idea so you don't have to. Some documentation is here.
看一下UIControl+BlocksKit.h的头文件。他们已经实现了戴夫·德龙的想法,所以你不必这么做。一些文档。
#7
1
Somebody is going to tell me why this is wrong, maybe, or with any luck, maybe not, so I'll either learn something, or I'll be helpful.
有人会告诉我为什么这是错的,也许,或者是运气,也许不是,所以我要么学一些东西,要么我就会帮上忙。
I just threw this together. It's really basic, just a thin-wrapper with a bit of casting. A word of warning, it assumes the block you're invoking has the correct signature to match the selector you use (i.e. number of arguments and 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
And
和
//
// 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
There's really nothing magical going on. Just lots of downcasting to void *
and typecasting to a usable block signature before invoking the method. Obviously (just like with performSelector:
and associated method, the possible combinations of inputs are finite, but extendable if you modify the code.
没有什么神奇的事情发生。在调用该方法之前,只需要大量的向下转换到void *并对可用的块签名进行类型转换。很明显(就像performSelector:和关联的方法一样,输入的可能组合是有限的,但是如果您修改了代码,则可扩展。
Used like this:
使用这样的:
BlockInvocation *invocation = [BlockInvocation invocationWithBlock:^(NSString *str) {
NSLog(@"Block was invoked with str = %@", str);
}];
[invocation performWithObject:@"Test"];
It outputs:
输出:
2011-01-03 16:11:16.020 BlockInvocation[37096:a0f] Block was invoked with str = Test
使用str = Test调用[37096:a0f]块。
Used in a target-action scenario you just need to do something like this:
在目标-动作场景中使用,您只需要做如下操作:
BlockInvocation *invocation = [[BlockInvocation alloc] initWithBlock:^(id sender) {
NSLog(@"Button with title %@ was clicked", [(NSButton *)sender title]);
}];
[myButton setTarget:invocation];
[myButton setAction:@selector(performWithObject:)];
Since the target in a target-action system is not retained, you will need to ensure the invocation object lives for as long as the control itself does.
由于目标-操作系统中的目标没有被保留,所以您需要确保调用对象的生存时间与控制本身一样长。
I'm interested to hear anything from somebody more expert than me.
我很想从比我更专业的人那里听到任何消息。
#8
1
I needed to have an action associated to a UIButton within a UITableViewCell. I wanted to avoid using tags to track down each button in every different cell. I thought the most direct way to achieve this was to associate a block "action" to the button like so:
我需要在UITableViewCell中有一个与UIButton相关联的操作。我希望避免使用标记来跟踪每个不同单元格中的每个按钮。我认为最直接的方法是将一个block“action”关联到按钮上:
[cell.trashButton addTarget:self withActionBlock:^{
NSLog(@"Will remove item #%d from cart!", indexPath.row);
...
}
forControlEvent:UIControlEventTouchUpInside];
My implementation is a bit more simplified, thanks to @bbum for mentioning imp_implementationWithBlock
and class_addMethod
, (although not extensively tested):
由于@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
#9
0
Doesn't it work to have an NSBlockOperation (iOS SDK +5). This code uses ARC and it is a simplification of an App I am testing this with (seems to work, at least apparently, not sure if I am leaking memory).
有一个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];
}
Of course, I am not sure how good this is for real usage. You need to keep a reference to the NSBlockOperation alive or I think that ARC will kill it.
当然,我不知道这对实际使用有多好。你需要保留对NSBlockOperation的引用,否则我认为它会杀死它。