我可以将block作为@selector使用Objective-C吗?

时间:2021-05-19 16:57:38

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:

这里做一些解释:

  1. 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.
  2. 我们使用的是自定义的“内部唯一”类,称为DDBlockActionWrapper。这是一个具有块属性(我们希望被调用的块)的简单类,以及一个简单地调用该块的方法。
  3. 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 its invokeBlock: method as the target and action (as normal).
  4. UIControl类别简单地实例化了其中一个包装器,给它一个被调用的块,然后告诉自己使用这个包装器和它的invokeBlock:方法作为目标和操作(正常)。
  5. The UIControl category uses an associated object to store an array of DDBlockActionWrappers, because UIControl does not retain its targets. This array is to ensure that the blocks exist when they're supposed to be invoked.
  6. UIControl类别使用一个相关联的对象来存储一组DDBlockActionWrappers,因为UIControl没有保留它的目标。这个数组是为了确保当它们被调用时,块是存在的。
  7. We have to ensure that the 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 original dealloc code. Tricky, tricky. Actually, associated objects are cleaned up automatically during deallocation.
  8. 我们必须确保当对象被销毁时,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:

这里做一些解释:

  1. 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.
  2. 我们使用的是自定义的“内部唯一”类,称为DDBlockActionWrapper。这是一个具有块属性(我们希望被调用的块)的简单类,以及一个简单地调用该块的方法。
  3. 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 its invokeBlock: method as the target and action (as normal).
  4. UIControl类别简单地实例化了其中一个包装器,给它一个被调用的块,然后告诉自己使用这个包装器和它的invokeBlock:方法作为目标和操作(正常)。
  5. The UIControl category uses an associated object to store an array of DDBlockActionWrappers, because UIControl does not retain its targets. This array is to ensure that the blocks exist when they're supposed to be invoked.
  6. UIControl类别使用一个相关联的对象来存储一组DDBlockActionWrappers,因为UIControl没有保留它的目标。这个数组是为了确保当它们被调用时,块是存在的。
  7. We have to ensure that the 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 original dealloc code. Tricky, tricky. Actually, associated objects are cleaned up automatically during deallocation.
  8. 我们必须确保当对象被销毁时,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的引用,否则我认为它会杀死它。