Могу ли я передать блок как @selector с Objective-C? - PullRequest
90 голосов
/ 03 января 2011

Можно ли передать блок Objective C для аргумента @selector в UIButton? т.е. есть ли способ заставить работать следующее?

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

Спасибо

Ответы [ 9 ]

69 голосов
/ 03 января 2011

Да, но вам придется использовать категорию.

Что-то вроде:

@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. Мы используем пользовательский класс "only only" с именем DDBlockActionWrapper.Это простой класс, который имеет свойство блока (блок, который мы хотим вызвать), и метод, который просто вызывает этот блок.
  2. Категория UIControl просто создает один из этих упаковщиков, дает емувызываемый блок, а затем говорит себе использовать эту оболочку и ее метод invokeBlock: в качестве цели и действия (как обычно).
  3. Категория UIControl использует связанный объект для хранения массива DDBlockActionWrappers, потому что UIControl не сохраняет свои цели.Этот массив должен гарантировать, что блоки существуют, когда они должны быть вызваны.
  4. Мы должны обеспечить очистку DDBlockActionWrappers при уничтожении объекта, поэтому мы делаемотвратительный взлом -[UIControl dealloc] с новым, который удаляет связанный объект, а затем вызывает оригинальный код dealloc.Tricky, tricky. На самом деле, связанные объекты автоматически очищаются при освобождении .

Наконец, этот код был набран в браузере и не был скомпилирован.Возможно, что-то не так с этим.Ваш пробег может отличаться.

41 голосов
/ 20 августа 2011

Блоки - это объекты.Передайте свой блок в качестве аргумента target, с @selector(invoke) в качестве аргумента action, например:

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

[button addTarget:block
           action:@selector(invoke)
 forControlEvents:UIControlEventTouchUpInside];
17 голосов
/ 03 января 2011

Нет, селекторы и блоки не являются совместимыми типами в Objective-C (на самом деле это разные вещи)Вам придется написать свой собственный метод и вместо него передать его селектор.

7 голосов
/ 19 мая 2012

Можно ли передать блок Objective-C для аргумента @selector в UIButton?

Принимая во внимание все уже предоставленные ответы, ответ да, но для настройки некоторых категорий требуется небольшая работа.

Я рекомендую использовать NSInvocation, потому что вы можете сделать многоес этим, например, с таймерами, хранящимися как объекты и вызываемыми ... и т. д.

Вот что я сделал, но учтите, что я использую ARC.

Во-первых, это простая категорияon 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-Методы.Например, вы можете использовать NSInvocationOperation (initWithInvocation :), NSTimer (scheduleTimerWithTimeInterval: invocation: repeatates:)

Точка превращения вашего блока в NSInvocation более универсальна и может использоваться следующим образом:

NSInvocation *invocation = [NSInvocation invocationWithTarget:self block:^(id target) {
                NSLog(@"My Block code here");
            }];
[button addTarget:invocation
           action:@selector(invoke)
 forControlEvents:UIControlEventTouchUpInside];

Опять же, это только одно предложение.

5 голосов
/ 03 января 2011

К сожалению, не все так просто.

Теоретически было бы возможно определить функцию, которая динамически добавляет метод к классу target, чтобы этот метод выполнял содержимое блока и возвращал селектор в соответствии с аргументом action. Эта функция может использовать технику, используемую MABlockClosure , которая, в случае iOS, зависит от пользовательской реализации libffi, которая все еще является экспериментальной.

Вам лучше реализовать действие как метод.

4 голосов
/ 08 июня 2013

Библиотека BlocksKit на Github (также доступна в виде CocoaPod) имеет встроенную функцию.

Посмотрите на заголовочный файл для UIControl + BlocksKit.h. Они реализовали идею Дэйва Делонга, так что вам не нужно. Некоторая документация здесь .

1 голос
/ 23 июля 2014

Мне нужно было действие, связанное с UIButton в UITableViewCell. Я хотел избежать использования тегов для отслеживания каждой кнопки в каждой отдельной ячейке. Я думал, что самый прямой способ добиться этого - связать блок «действие» с кнопкой следующим образом:

[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
1 голос
/ 03 января 2011

Кто-то скажет мне, почему это неправильно, может быть, или, если повезет, может, нет, поэтому я либо чему-то научусь, либо помогу.

Я просто бросил это вместе. Это действительно просто, просто тонкая обертка с небольшим количеством заливки. Предупреждающее слово: предполагается, что вызываемый вами блок имеет правильную сигнатуру, соответствующую выбранному вами селектору (то есть числу аргументов и типов).

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

Поскольку цель в системе целевого действия не сохраняется, вам необходимо убедиться, что вызывающий объект живет так же долго, как и сам элемент управления.

Мне интересно что-нибудь услышать от кого-то более опытного, чем я.

0 голосов
/ 02 марта 2013

Не работает иметь 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 убьет его.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...