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

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

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

감사



답변

예,하지만 카테고리를 사용해야합니다.

다음과 같은 것 :

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

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


답변

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

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

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


답변

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


답변

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

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


답변

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

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

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


답변

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

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


답변

누군가가 왜 이것이 잘못된 것인지, 아니면 운이 좋을지 말해 줄 것입니다. 그래서 저는 무언가를 배우거나 도움이 될 것입니다.

나는 이것을 함께 던졌다. 약간의 캐스팅이있는 얇은 래퍼입니다. 경고의 한마디로, 호출하는 블록에 사용하는 선택자와 일치하는 올바른 서명이 있다고 가정합니다 (예 : 인수 및 유형의 수).

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

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

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