Programing

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

lottogame 2020. 9. 5. 10:29
반응형

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

There's really nothing magical going on. Just lots of downcasting to void * and typecasting to a usable block signature before invoking the method. Obviously (just like with performSelector: and associated method, the possible combinations of inputs are finite, but extendable if you modify the code.

Used like this:

BlockInvocation *invocation = [BlockInvocation invocationWithBlock:^(NSString *str) {
    NSLog(@"Block was invoked with str = %@", str);
}];
[invocation performWithObject:@"Test"];

It outputs:

2011-01-03 16:11:16.020 BlockInvocation[37096:a0f] Block was invoked with str = Test

Used in a target-action scenario you just need to do something like this:

BlockInvocation *invocation = [[BlockInvocation alloc] initWithBlock:^(id sender) {
  NSLog(@"Button with title %@ was clicked", [(NSButton *)sender title]);
}];
[myButton setTarget:invocation];
[myButton setAction:@selector(performWithObject:)];

Since the target in a target-action system is not retained, you will need to ensure the invocation object lives for as long as the control itself does.

I'm interested to hear anything from somebody more expert than me.


I needed to have an action associated to a UIButton within a UITableViewCell. I wanted to avoid using tags to track down each button in every different cell. I thought the most direct way to achieve this was to associate a block "action" to the button like so:

[cell.trashButton addTarget:self withActionBlock:^{
        NSLog(@"Will remove item #%d from cart!", indexPath.row);
        ...
    }
    forControlEvent:UIControlEventTouchUpInside];

My implementation is a bit more simplified, thanks to @bbum for mentioning imp_implementationWithBlock and class_addMethod, (although not extensively tested):

#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

Doesn't it work to have an NSBlockOperation (iOS SDK +5). This code uses ARC and it is a simplification of an App I am testing this with (seems to work, at least apparently, not sure if I am leaking memory).

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

Of course, I am not sure how good this is for real usage. You need to keep a reference to the NSBlockOperation alive or I think that ARC will kill it.

참고 URL : https://stackoverflow.com/questions/4581782/can-i-pass-a-block-as-a-selector-with-objective-c

반응형