Objective-C를 사용하여 블록을 @selector로 전달할 수 있습니까?


90

@selector인수에 대한 Objective-C 블록을 전달할 수 UIButton있습니까? 즉, 다음을 작동시킬 수있는 방법이 있습니까?

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

감사

답변:


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코드 를 호출하는 새 것으로 엉망인 해킹을 하고 있습니다. 까다 롭고 까다 롭습니다. 실제로 관련 객체는 할당 해제 중에 자동으로 정리됩니다 .

마지막으로,이 코드는 브라우저에 입력되었으며 컴파일되지 않았습니다. 아마 몇 가지 문제가있을 것입니다. 귀하의 마일리지가 다를 수 있습니다.


4
이제 연관 객체를 사용하는 것보다 약간 더 효율적인 방식으로이 문제를 사용 objc_implementationWithBlock()하고 class_addMethod()해결할 수 있습니다 (메서드 조회만큼 효율적이지 않은 해시 조회를 의미 함). 무관 한 성능 차이 일 수 있지만 대안입니다.
bbum

@bbum 의미 imp_implementationWithBlock합니까?
vikingosegundo

그래, 그거. 한때 이름이 objc_implementationWithBlock(). :)
bbum

custom UITableViewCell의 버튼에 이것을 사용하면 모든 새 대상이 새 인스턴스이고 이전 대상이 동일한 이벤트에 대해 정리되지 않기 때문에 원하는 대상 작업이 중복됩니다. 먼저 목표물을 청소해야합니다 for (id t in self.allTargets) { [self removeTarget:t action:@selector(invokeBlock:) forControlEvents:controlEvents]; } [self addTarget:target action:@selector(invokeBlock:) forControlEvents:controlEvents];
Eugene

위의 코드를 더 명확하게 만드는 한 가지는 UIControl이 많은 target : action 쌍을 받아 들일 수 있다는 것을 아는 것입니다. 따라서 모든 쌍을 저장하기 위해 가변 배열을 만들어야합니다
abbood

41

블록은 객체입니다. 다음 target@selector(invoke)같이 블록을 인수 로 action전달하십시오.

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

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

그 흥미 롭군요. 오늘 밤에 비슷한 일을 할 수 있는지 알아 보겠습니다. 새로운 질문을 시작할 수 있습니다.
Tad Donaghe 2011-08-24

31
이것은 우연히 "작동"합니다. 개인 API에 의존합니다. invokeBlock 객체 의 메서드는 공개되지 않으며 이러한 방식으로 사용할 수 없습니다.
bbum

1
Bbum : 맞아요. -invoke가 공개 된 것으로 생각했지만 내 답변을 업데이트하고 버그를 신고하려고했습니다.
lemnar

1
굉장한 솔루션처럼 보이지만 개인 API를 사용하기 때문에 Apple에서 허용하는지 궁금합니다.
Brian

1
nil대신 전달되면 작동합니다 @selector(invoke).
k06a

17

아니요, 선택기와 블록은 Objective-C에서 호환되는 유형이 아닙니다 (사실 매우 다른 것입니다). 자신 만의 메서드를 작성하고 대신 선택기를 전달해야합니다.


11
특히 선택자는 실행하는 것이 아닙니다. 객체에 보내는 메시지의 이름입니다 (또는 다른 객체가 세 번째 객체로 보내도록합니다.이 경우에는 컨트롤에 [selector goes here] 메시지를 대상에 보내도록 지시합니다). 반면에 블록 실행하는 것입니다. 객체와 관계없이 블록을 직접 호출합니다.
Peter Hosey 2011 년

7

UIButton의 @selector 인수에 대한 Objective-C 블록을 전달할 수 있습니까?

이미 제공된 모든 답변을 취하면 대답은 예이지만 일부 범주를 설정하려면 약간의 작업이 필요합니다.

NSInvocation을 사용하는 것이 좋습니다. 타이머를 사용하는 것과 같이 많은 일을 할 수 있기 때문입니다. 객체로 저장되고 호출됩니다.

여기 내가 한 일이 있지만 ARC를 사용하고 있습니다.

첫 번째는 NSObject의 간단한 카테고리입니다.

.h

@interface NSObject (CategoryNSObject)

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

@end

.미디엄

#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

.미디엄

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

다시 말하지만 이것은 하나의 제안 일뿐입니다.


한 가지 더, 여기에서 호출하는 것은 공용 메서드입니다. developer.apple.com/library/mac/#documentation/Cocoa/Reference/…
Arvin

5

안타깝게도 그렇게 간단하지는 않습니다.

이론적으로는의 클래스에 메서드를 동적으로 추가하고 target해당 메서드가 블록의 내용을 실행하고 action인수에서 필요에 따라 선택기를 반환하는 함수를 정의 할 수 있습니다 . 이 함수는 MABlockClosure 에서 사용하는 기술을 사용할 수 있습니다. iOS의 경우 아직 실험적인 libffi의 사용자 지정 구현에 의존합니다.

액션을 메소드로 구현하는 것이 좋습니다.


4

Github 의 라이브러리 BlocksKit ( CocoaPod 으로도 사용 가능)에는이 기능이 내장되어 있습니다.

UIControl + BlocksKit.h의 헤더 파일을 살펴보십시오. 그들은 Dave DeLong의 아이디어를 구현 했으므로 필요하지 않습니다. 일부 문서는 여기에 있습니다 .


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 *메서드를 호출하기 전에 사용 가능한 블록 서명에 대한 다운 캐스팅 과 타입 캐스팅이 많습니다. 분명히 (with 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:)];

타겟-액션 시스템의 타겟이 유지되지 않기 때문에, 컨트롤 자체가하는 한 호출 객체가 살아 있는지 확인해야합니다.

나는 나보다 더 전문적인 누군가의 이야기를 듣고 싶습니다.


때문에 해당 타겟 - 액션 시나리오에서 메모리 누수가 invocation해제되지 않습니다
user102008

1

UITableViewCell 내에서 UIButton에 연결된 작업이 필요했습니다. 태그를 사용하여 모든 셀의 각 버튼을 추적하는 것을 피하고 싶었습니다. 이를 달성하는 가장 직접적인 방법은 블록 "액션"을 다음과 같이 버튼에 연결하는 것이라고 생각했습니다.

[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

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가이를 죽일 것이라고 생각합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.