关于cocoa-touch:我可以在Objective-C中以@selector的形式传递块吗?

Can I pass a block as a @selector with Objective-C?

是否可以在UIButton中为@selector参数传递一个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

一些解释:

  • 我们正在使用称为DDBlockActionWrapper的自定义"仅内部"类。这是一个简单的类,具有一个块属性(我们要调用的块)和一个简单地调用该块的方法。
  • UIControl类别仅实例化这些package器之一,为它提供要调用的块,然后告诉自己使用该package器及其invokeBlock:方法作为目标和操作(正常)。
  • UIControl类别使用关联的对象来存储DDBlockActionWrappers的数组,因为UIControl不会保留其目标。该数组用于确保在应该调用它们时存在这些块。
  • 我们必须确保在销毁对象时将DDBlockActionWrappers清理干净,因此我们正在进行令人讨厌的黑客攻击,用新的-[UIControl dealloc]来删除-[UIControl dealloc],该新对象将删除关联的对象,然后调用原始对象。 dealloc代码。棘手,棘手。实际上,关联对象在释放过程中会自动清理。
  • 最后,此代码是在浏览器中键入的,尚未编译。可能有些问题。您的里程可能会有所不同。


    块是对象。将块作为target参数传递,将@selector(invoke)作为action参数传递,如下所示:

    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];

    同样,这只是一个建议。


    不幸的是,事情并非如此简单。

    从理论上讲,可以定义一个函数,该函数可以动态地向target类添加方法,让该方法执行块的内容,并根据action参数的需要返回选择器。此功能可以使用MABlockClosure所使用的技术,在iOS上,该技术取决于libffi的自定义实现,而该实现仍处于实验阶段。

    您最好将动作作为方法来实现。


    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提到了imp_implementationWithBlockclass_addMethod,(虽然未经广泛测试),我的实现更加简化了:

    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

    真的没有什么神奇的事情发生。在调用该方法之前,只需要大量向下转换到void *并类型转换到可用的块签名。显然(与performSelector:和相关方法一样,输入的可能组合是有限的,但是如果您修改代码,则可以扩展。

    像这样使用:

    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将杀死它。