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

Bil*_*iff 90 cocoa-touch objective-c

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

    [closeOverlayButton addTarget:self 
                           action:^ {[anotherIvarLocalToThisMethod removeFromSuperview];} 
                 forControlEvents:UIControlEventTouchUpInside];
Run Code Online (Sandbox Code Playgroud)

谢谢

Dav*_*ong 69

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

就像是:

@interface UIControl (DDBlockActions)

- (void) addEventHandler:(void(^)(void))handler 
        forControlEvents:(UIControlEvents)controlEvents;

@end
Run Code Online (Sandbox Code Playgroud)

实现会有点棘手:

#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
Run Code Online (Sandbox Code Playgroud)

一些解释:

  1. 我们使用的是自定义的"仅限内部"类DDBlockActionWrapper.这是一个简单的类,它具有一个块属性(我们想要调用的块),以及一个简单地调用该块的方法.
  2. UIControl类别简单地实例化这些包装中的一个,将其提供给被调用的块,然后告诉本身使用该包装件和其invokeBlock:作为目标和动作方法(正常).
  3. UIControl类别使用关联对象来存储数组DDBlockActionWrappers,因为UIControl它不保留其目标.此数组用于确保在应该调用块时存在块.
  4. 我们必须确保DDBlockActionWrappers在对象被销毁时清理它,所以我们正在做一个令人讨厌的黑客-[UIControl dealloc]用一个新的删除关联对象,然后调用原始dealloc代码.棘手,棘手. 实际上,在重新分配期间会自动清理关联的对象.

最后,此代码在浏览器中输入,尚未编译.它可能有些问题.你的旅费可能会改变.

  • 请注意,您现在可以使用`objc_implementationWithBlock()`和`class_addMethod()`以比使用关联对象更有效的方式解决此问题(这意味着哈希查找不如方法查找有效).可能是一个无关紧要的性能差异,但它是另一种选择. (4认同)

lem*_*nar 41

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

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

[button addTarget:block
           action:@selector(invoke)
 forControlEvents:UIControlEventTouchUpInside];
Run Code Online (Sandbox Code Playgroud)

  • 这"巧妙地起作用".它依赖于私有API; Block对象上的`invoke`方法不公开,不打算以这种方式使用. (31认同)

Bol*_*ock 17

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

  • 特别是,选择器不是你执行的东西; 它是您发送给对象的消息的名称(或者将另一个对象发送到第三个对象,如本例所示:您告诉控件将[selector goes here]消息发送给目标).另一方面,块是*你执行的东西:你直接调用块,独立于一个对象. (11认同)

Arv*_*vin 7

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

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

我建议使用NSInvocation因为你可以做很多事情,比如定时器,存储为对象和调用...等...

这是我做的,但请注意我使用的是ARC.

首先是NSObject上的一个简单类别:

.H

@interface NSObject (CategoryNSObject)

- (void) associateValue:(id)value withKey:(NSString *)aKey;
- (id) associatedValueForKey:(NSString *)aKey;

@end
Run Code Online (Sandbox Code Playgroud)

.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
Run Code Online (Sandbox Code Playgroud)

接下来是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
Run Code Online (Sandbox Code Playgroud)

.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
Run Code Online (Sandbox Code Playgroud)

以下是如何使用它:

NSInvocation *invocation = [NSInvocation invocationWithTarget:self block:^(id target) {
            NSLog(@"TEST");
        }];
[invocation invoke];
Run Code Online (Sandbox Code Playgroud)

您可以使用调用和标准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];
Run Code Online (Sandbox Code Playgroud)

这只是一个建议.


小智 5

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

从理论上讲,可以定义一个函数,该函数动态地向该类添加一个方法target,让该方法执行一个块的内容,并根据action参数的需要返回一个选择器.这个函数可以使用MABlockClosure使用的技术,在iOS的情况下,它依赖于libffi的自定义实现,这仍然是实验性的.

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