Tôi có thể chuyển một khối dưới dạng @selector với Objective-C không?


90

Có thể chuyển một khối Objective-C cho @selectorđối số trong a UIButtonkhông? tức là, Có cách nào để có được những điều sau đây để làm việc?

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

Cảm ơn

Câu trả lời:


69

Có, nhưng bạn phải sử dụng một danh mục.

Cái gì đó như:

@interface UIControl (DDBlockActions)

- (void) addEventHandler:(void(^)(void))handler 
        forControlEvents:(UIControlEvents)controlEvents;

@end

Việc triển khai sẽ phức tạp hơn một chút:

#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

Một số giải thích:

  1. Chúng tôi đang sử dụng một lớp "chỉ nội bộ" tùy chỉnh được gọi DDBlockActionWrapper. Đây là một lớp đơn giản có thuộc tính khối (khối mà chúng ta muốn gọi) và một phương thức chỉ đơn giản gọi khối đó.
  2. Các UIControlloại đơn giản instantiates một trong những giấy gói, cung cấp cho nó các khối được gọi, và sau đó nói với bản thân để sử dụng wrapper và nó invokeBlock:phương pháp làm mục tiêu và hành động (như bình thường).
  3. Các UIControlloại sử dụng một đối tượng liên quan để lưu trữ một mảng DDBlockActionWrappers, vì UIControlkhông giữ lại các mục tiêu của nó. Mảng này để đảm bảo rằng các khối tồn tại khi chúng được cho là được gọi.
  4. Chúng tôi phải đảm bảo rằng DDBlockActionWrappersđối tượng được dọn dẹp sạch sẽ khi đối tượng bị phá hủy, vì vậy chúng tôi đang thực hiện một cuộc tấn công khó chịu khi sử dụng một đối tượng -[UIControl dealloc]mới để xóa đối tượng được liên kết và sau đó gọi deallocmã gốc . Mưu mẹo, gian manh. Trên thực tế, các đối tượng liên quan được dọn dẹp tự động trong quá trình phân bổ .

Cuối cùng, mã này đã được nhập trong trình duyệt và chưa được biên dịch. Có thể có một số điều sai với nó. Số dặm của bạn có thể thay đổi.


4
Lưu ý rằng bây giờ bạn có thể sử dụng objc_implementationWithBlock()class_addMethod()giải quyết vấn đề này theo cách hiệu quả hơn một chút so với việc sử dụng các đối tượng được liên kết (ngụ ý tra cứu băm không hiệu quả như tra cứu phương pháp). Có thể là một sự khác biệt hiệu suất không liên quan, nhưng nó là một sự thay thế.
bbum

ý bạn là @bbum imp_implementationWithBlock?
vikingosegundo

Ừ - cái đó. Nó đã từng được đặt tên objc_implementationWithBlock(). :)
bbum

Sử dụng điều này cho các nút trong tùy chỉnh UITableViewCellsẽ dẫn đến sự trùng lặp của các hành động mục tiêu mong muốn vì mỗi mục tiêu mới là một phiên bản mới và các mục tiêu trước đó không được làm sạch cho các sự kiện giống nhau. Bạn đã gotta làm sạch các mục tiêu đầu tiên for (id t in self.allTargets) { [self removeTarget:t action:@selector(invokeBlock:) forControlEvents:controlEvents]; } [self addTarget:target action:@selector(invokeBlock:) forControlEvents:controlEvents];
Eugene

Tôi nghĩ rằng một điều mà làm cho mã trên rõ ràng hơn là biết rằng một UIControl có thể chấp nhận nhiều mục tiêu: cặp hành động .. vì vậy cần phải tạo ra một mảng có thể thay đổi để lưu trữ tất cả những cặp
abbood

41

Các khối là các đối tượng. Chuyển khối của bạn làm targetđối số, với @selector(invoke)làm actionđối số, như sau:

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

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

Nó thật thú vị. Tôi sẽ xem xét liệu tôi có thể làm điều gì đó tương tự tối nay không. Có thể bắt đầu một câu hỏi mới.
Tad Donaghe

31
Điều này "hoạt động" bởi sự trùng hợp. Nó dựa trên API riêng; các invokephương pháp trên các đối tượng Block là không được công khai và không có ý định sẽ được sử dụng theo cách này.
bbum

1
Bbum: Bạn nói đúng. Tôi đã nghĩ -invoke là công khai, nhưng tôi có ý định cập nhật câu trả lời của mình và gửi lỗi.
lemnar

1
nó có vẻ là một giải pháp tuyệt vời, nhưng tôi đang tự hỏi liệu nó có được Apple chấp nhận hay không vì nó sử dụng một API riêng.
Brian

1
Hoạt động khi được thông qua nilthay vì @selector(invoke).
k06a

17

Không, bộ chọn và khối không phải là loại tương thích trong Objective-C (trên thực tế, chúng rất khác nhau). Thay vào đó, bạn sẽ phải viết phương thức của riêng mình và chuyển bộ chọn của nó.


11
Đặc biệt, một bộ chọn không phải là thứ bạn thực thi; nó là tên của thông điệp bạn gửi đến một đối tượng (hoặc có một đối tượng khác gửi đến đối tượng thứ ba, như trong trường hợp này: bạn đang yêu cầu bộ điều khiển gửi một thông báo [selector go here] tới đích). Mặt khác, một khối thứ bạn thực thi: Bạn gọi khối trực tiếp, độc lập với một đối tượng.
Peter Hosey

7

Có thể chuyển một khối Objective-C cho đối số @selector trong UIButton không?

Lấy tất cả các câu trả lời đã được cung cấp, câu trả lời là Có nhưng một chút công việc là cần thiết để thiết lập một số danh mục.

Tôi khuyên bạn nên sử dụng NSInvocation vì bạn có thể làm được nhiều việc với điều này chẳng hạn như với bộ định thời, được lưu trữ dưới dạng một đối tượng và được gọi ra ... vv ...

Đây là những gì tôi đã làm, nhưng lưu ý rằng tôi đang sử dụng ARC.

Đầu tiên là một danh mục đơn giản trên 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

Tiếp theo là một danh mục trên NSInvocation để lưu trữ trong một khối:

.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

Đây là cách sử dụng nó:

NSInvocation *invocation = [NSInvocation invocationWithTarget:self block:^(id target) {
            NSLog(@"TEST");
        }];
[invocation invoke];

Bạn có thể làm được nhiều điều với lời gọi và các Phương thức Objective-C tiêu chuẩn. Ví dụ: bạn có thể sử dụng NSInvocationOperation (initWithInvocation :), NSTimer (isedTimerWithTimeInterval: invocation: lặp lại :)

Điểm mấu chốt là biến khối của bạn thành một NSInvocation linh hoạt hơn và có thể được sử dụng như sau:

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

Một lần nữa đây chỉ là một gợi ý.


Một điều nữa, gọi ở đây là một phương thức công khai. developer.apple.com/library/mac/#documentation/Cocoa/Reference/…
Arvin

5

Không đơn giản như vậy, thật không may.

Về lý thuyết, có thể xác định một hàm tự động thêm một phương thức vào lớp của target, để phương thức đó thực thi nội dung của một khối và trả về một bộ chọn khi actionđối số cần. Chức năng này có thể sử dụng kỹ thuật được sử dụng bởi MABlockClosure , trong trường hợp của iOS, phụ thuộc vào việc triển khai tùy chỉnh của libffi, vẫn còn đang thử nghiệm.

Tốt hơn hết bạn nên triển khai hành động như một phương pháp.


4

Thư viện BlocksKit trên Github (cũng có sẵn dưới dạng CocoaPod) được tích hợp sẵn tính năng này.

Hãy xem tệp tiêu đề cho UIControl + BlocksKit.h. Họ đã thực hiện ý tưởng của Dave DeLong nên bạn không cần phải làm thế. Một số tài liệu ở đây .


1

Ai đó sẽ cho tôi biết tại sao điều này là sai, có thể, hoặc với bất kỳ sự may mắn nào, có thể không, vì vậy tôi sẽ học được điều gì đó, hoặc tôi sẽ có ích.

Tôi chỉ ném cái này cùng nhau. Nó thực sự cơ bản, chỉ là một lớp bọc mỏng với một chút đúc. Một lời cảnh báo, nó giả định rằng khối bạn đang gọi có chữ ký chính xác để khớp với bộ chọn bạn sử dụng (tức là số đối số và kiểu).

//
//  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

Thực sự không có gì kỳ diệu xảy ra. Chỉ cần rất nhiều dự báo xuống void *và đánh máy cho một chữ ký khối có thể sử dụng trước khi gọi phương thức. Rõ ràng là (giống như với performSelector:và phương thức liên kết, các kết hợp đầu vào có thể có là hữu hạn, nhưng có thể mở rộng nếu bạn sửa đổi mã.

Được sử dụng như thế này:

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

Nó xuất ra:

2011-01-03 16: 11: 16.020 BlockInvocation [37096: a0f] Block đã được gọi với str = Test

Được sử dụng trong một kịch bản hành động mục tiêu, bạn chỉ cần làm như sau:

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

Vì mục tiêu trong hệ thống hành động đích không được giữ lại, bạn sẽ cần đảm bảo đối tượng gọi tồn tại miễn là điều khiển tự nó hoạt động.

Tôi muốn nghe bất cứ điều gì từ ai đó chuyên gia hơn tôi.


bạn bị rò rỉ bộ nhớ trong kịch bản hành động đích đó vì invocationnó không bao giờ được phát hành
user102008

1

Tôi cần có một hành động được liên kết với UIButton trong UITableViewCell. Tôi muốn tránh sử dụng các thẻ để theo dõi từng nút trong mỗi ô khác nhau. Tôi nghĩ cách trực tiếp nhất để đạt được điều này là liên kết "hành động" khối với nút như sau:

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

Cách triển khai của tôi đơn giản hơn một chút, cảm ơn @bbum đã đề cập imp_implementationWithBlockclass_addMethod(mặc dù không được thử nghiệm rộng rãi):

#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

Không hoạt động khi có NSBlockOperation (iOS SDK +5). Mã này sử dụng ARC và nó là sự đơn giản hóa của một Ứng dụng mà tôi đang thử nghiệm này (dường như hoạt động, ít nhất là rõ ràng, không chắc liệu tôi có bị rò rỉ bộ nhớ hay không).

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

Tất nhiên, tôi không chắc điều này tốt như thế nào để sử dụng thực sự. Bạn cần giữ một tham chiếu đến NSBlockOperation còn sống nếu không tôi nghĩ rằng ARC sẽ giết nó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.