是否可以为@selector
中的UIButton
参数传递Objective-C块?即,有没有办法让以下工作?
[closeOverlayButton addTarget:self
action:^ {[anotherIvarLocalToThisMethod removeFromSuperview];}
forControlEvents:UIControlEventTouchUpInside];
由于
答案 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
一些解释:
DDBlockActionWrapper
的自定义“仅限内部”类。这是一个简单的类,它有一个块属性(我们想要调用的块),以及一个只调用该块的方法。UIControl
类只是实例化其中一个包装器,为它提供要调用的块,然后告诉自己使用该包装器及其invokeBlock:
方法作为目标和操作(正常) UIControl
类别使用关联的对象来存储DDBlockActionWrappers
的数组,因为UIControl
不会保留其目标。这个数组是为了确保在调用它们时存在块。DDBlockActionWrappers
,所以我们正在做一个令人讨厌的黑客攻击-[UIControl dealloc]
一个新的删除关联对象,然后调用原始的dealloc
代码。棘手,棘手。最后,此代码在浏览器中输入,尚未编译。它可能有些问题。您的里程可能会有所不同。
答案 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_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
答案 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会杀死它。