我可以使用Objective-C将块作为@selector传递吗?

时间:2011-01-03 02:53:11

标签: objective-c cocoa-touch

是否可以为@selector中的UIButton参数传递Objective-C块?即,有没有办法让以下工作?

    [closeOverlayButton addTarget:self 
                           action:^ {[anotherIvarLocalToThisMethod removeFromSuperview];} 
                 forControlEvents:UIControlEventTouchUpInside];

由于

9 个答案:

答案 0 :(得分:69)

是的,但你必须使用一个类别。

类似的东西:

@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

一些解释:

  1. 我们正在使用名为DDBlockActionWrapper的自定义“仅限内部”类。这是一个简单的类,它有一个块属性(我们想要调用的块),以及一个只调用该块的方法。
  2. UIControl类只是实例化其中一个包装器,为它提供要调用的块,然后告诉自己使用该包装器及其invokeBlock:方法作为目标和操作(正常)
  3. UIControl类别使用关联的对象来存储DDBlockActionWrappers的数组,因为UIControl不会保留其目标。这个数组是为了确保在调用它们时存在块。
  4. 我们必须确保在对象被销毁时清除DDBlockActionWrappers,所以我们正在做一个令人讨厌的黑客攻击-[UIControl dealloc]一个新的删除关联对象,然后调用原始的dealloc代码。棘手,棘手。实际上,associated objects are cleaned up automatically during deallocation
  5. 最后,此代码在浏览器中输入,尚未编译。它可能有些问题。您的里程可能会有所不同。

答案 1 :(得分:41)

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

id block = [^{NSLog(@"Hello, world");} copy];// Don't forget to -release.

[button addTarget:block
           action:@selector(invoke)
 forControlEvents:UIControlEventTouchUpInside];

答案 2 :(得分:17)

不,选择器和块在Objective-C中不兼容(实际上,它们是非常不同的东西)。你必须编写自己的方法并改为传递它的选择器。

答案 3 :(得分:7)

  

是否可以在UIButton中传递@selector参数的Objective-C块?

接受所有已提供的答案,答案是肯定的,但是设置某些类别需要做一些工作。

我建议使用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];

这只是一个建议。

答案 4 :(得分:5)

不幸的是,并不那么简单。

理论上,可以定义一个函数,该函数动态地将方法添加到target的类中,让该方法执行块的内容,并根据需要返回{{1参数。这个函数可以使用MABlockClosure使用的技术,在iOS的情况下,它取决于libffi的自定义实现,它仍然是实验性的。

你最好把这个动作作为一种方法来实现。

答案 5 :(得分:4)

Github上的库BlocksKit(也可作为CocoaPod提供)内置了此功能。

查看UIControl + BlocksKit.h的头文件。他们实施了Dave DeLong的想法,所以你不必这样做。有些文档是here

答案 6 :(得分:1)

有人会告诉我为什么这是错误的,或许是运气好,也许不是,所以我要么学到一些东西,要么我会有所帮助。

我把它扔到了一起。这是非常基本的,只是一个带有一点铸造的薄包装。一句警告,它​​假定您正在调用的块具有正确的签名以匹配您使用的选择器(即参数和类型的数量)。

//
//  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 *进行大量向下转换并将其转换为可用的块签名。显然(就像使用performSelector:和相关方法一样,输入的可能组合是有限的,但如果修改代码,则可以扩展。

像这样使用:

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

由于目标 - 操作系统中的目标未被保留,因此您需要确保调用对象的存在时间与控件本身一样长。

我很想听到比我更专家的任何事情。

答案 7 :(得分:1)

我需要在UITableViewCell中有一个与UIButton关联的动作。我想避免使用标签来跟踪每个不同单元格中的每个按钮。我认为实现这一目标最直接的方法是关联一个块&#34;动作&#34;到这样的按钮:

[cell.trashButton addTarget:self withActionBlock:^{
        NSLog(@"Will remove item #%d from cart!", indexPath.row);
        ...
    }
    forControlEvent:UIControlEventTouchUpInside];

由于@bbum提及imp_implementationWithBlockclass_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

答案 8 :(得分:0)

不能使用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];
}

当然,我不确定这对于实际使用有多好。你需要保持对NSBlockOperation的引用,或者我认为ARC会杀死它。