क्या मैं ऑब्जेक्टिव-सी के साथ @ ब्लॉक के रूप में ब्लॉक पास कर सकता हूं?


90

क्या @selectorए में तर्क के लिए ऑब्जेक्टिव-सी ब्लॉक पास करना संभव है 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()। :)
बबूल २um

कस्टम के बटन के लिए इसका उपयोग UITableViewCellकरने से वांछित लक्ष्यों-कार्यों के दोहराव का परिणाम होगा क्योंकि हर नया लक्ष्य एक नया उदाहरण है और पिछले वाले समान घटनाओं के लिए साफ नहीं होते हैं। आपने पहले लक्ष्यों को साफ किया होगा for (id t in self.allTargets) { [self removeTarget:t action:@selector(invokeBlock:) forControlEvents:controlEvents]; } [self addTarget:target action:@selector(invokeBlock:) forControlEvents:controlEvents];
यूजीन

मुझे लगता है कि एक बात उपरोक्त कोड अधिक स्पष्ट करता है कि जानते हुए भी जाता है कि एक UIControl कई लक्ष्य को स्वीकार कर सकते हैं: कार्रवाई जोड़े .. इसलिए उन सभी जोड़ों को स्टोर करने के लिए एक परिवर्तनशील सरणी बनाने की आवश्यकता
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];

यह तो दिलचस्प है। मैं देखूंगा कि क्या मैं आज रात कुछ ऐसा कर सकता हूं। एक नया प्रश्न शुरू कर सकते हैं।
तद डोंगे

31
संयोग से यह "काम" करता है। यह निजी एपीआई पर निर्भर करता है; invokeब्लॉक वस्तुओं पर विधि सार्वजनिक और नहीं इस फैशन में इस्तेमाल किया जा करने का इरादा नहीं है।
bbum

1
Bbum: तुम सही हो। मैंने सोचा था कि -इनवोक सार्वजनिक थी, लेकिन मुझे अपने जवाब को अपडेट करने और बग दर्ज करने का अर्थ है।
लेमनर

1
यह एक भयानक समाधान लगता है, लेकिन मुझे आश्चर्य है कि क्या यह Apple द्वारा स्वीकार्य है क्योंकि यह एक निजी एपीआई का उपयोग करता है।
ब्रायन

1
के nilबजाय जब पारित कर दिया काम करता है @selector(invoke)
k06a

17

नहीं, चयनकर्ता और ब्लॉक ऑब्जेक्टिव-सी में संगत प्रकार नहीं हैं (वास्तव में, वे बहुत अलग चीजें हैं)। आपको अपनी स्वयं की विधि लिखनी होगी और इसके बजाय इसका चयनकर्ता पास करना होगा।


11
विशेष रूप से, एक चयनकर्ता ऐसा कुछ नहीं है जिसे आप निष्पादित करते हैं; यह उस संदेश का नाम है जिसे आप किसी ऑब्जेक्ट को भेजते हैं (या किसी अन्य ऑब्जेक्ट को किसी तीसरे ऑब्जेक्ट को भेजते हैं, जैसा कि इस मामले में है: आप नियंत्रण को [चयनकर्ता को यहां भेजते हैं] संदेश को लक्ष्य पर भेजने के लिए कह रहे हैं)। दूसरी ओर, एक ब्लॉक, कुछ ऐसा है जिसे आप निष्पादित करते हैं: आप ब्लॉक को सीधे, एक वस्तु से स्वतंत्र रूप से कहते हैं।
पीटर होसी

7

क्या UIButton में @selector तर्क के लिए ऑब्जेक्टिव-सी ब्लॉक पास करना संभव है?

पहले से उपलब्ध सभी उत्तरों में लेते हुए, उत्तर हां है लेकिन कुछ श्रेणियों को सेटअप करने के लिए काम का एक छोटा सा हिस्सा आवश्यक है।

मैं NSInvocation का उपयोग करने की सलाह देता हूं क्योंकि आप इसके साथ बहुत कुछ कर सकते हैं जैसे कि टाइमर के साथ, एक वस्तु के रूप में संग्रहीत और आह्वान ... आदि।

यहाँ मैंने क्या किया है, लेकिन ध्यान दें मैं एआरसी का उपयोग कर रहा हूं।

पहले NSObject पर एक सरल श्रेणी है:

@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 पर एक श्रेणी है:

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

आप आह्वान और मानक उद्देश्य-सी विधियों के साथ बहुत कुछ कर सकते हैं। उदाहरण के लिए, आप NSInvocationOperation (initWithInvocation :), NSTimer (शेड्यूल किए गए WithTimeInterval: मंगलाचरण: रिपीट :) का उपयोग कर सकते हैं

बिंदु आपके ब्लॉक को NSInvocation में बदल रहा है और अधिक बहुमुखी है और इसे इस तरह इस्तेमाल किया जा सकता है:

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

फिर से यह सिर्फ एक सुझाव है।


एक और बात, यहां आह्वान एक सार्वजनिक तरीका है। डेवलपर
Arvin

5

उतना सरल नहीं है, दुर्भाग्य से।

सिद्धांत रूप में, एक फ़ंक्शन को परिभाषित करना संभव होगा जो गतिशील रूप से वर्ग के लिए एक विधि जोड़ता है target, उस पद्धति में एक ब्लॉक की सामग्री को निष्पादित करता है, और actionतर्क द्वारा आवश्यकतानुसार चयनकर्ता को वापस लौटाता है । यह फ़ंक्शन MlocklockClosure द्वारा उपयोग की जाने वाली तकनीक का उपयोग कर सकता है , जो कि iOS के मामले में, libffi के कस्टम कार्यान्वयन पर निर्भर करता है, जो अभी भी प्रायोगिक है।

आप एक विधि के रूप में कार्रवाई को लागू करने से बेहतर हैं।


4

लाइब्रेरी ब्लॉकशिट ऑन गीथब (एक कोकोआपोड के रूप में भी उपलब्ध है) में यह सुविधा अंतर्निहित है।

UIControl + BlocksKit.h के हेडर फ़ाइल पर एक नज़र डालें। उन्होंने डेव देलांग के विचार को लागू किया है इसलिए आपको नहीं करना है। कुछ प्रलेखन यहाँ है


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 *विधि को लागू करने से पहले एक प्रयोग करने योग्य ब्लॉक हस्ताक्षर के लिए और टाइपकास्टिंग के । जाहिर है (बस के साथ की तरह)performSelector: और संबंधित विधि की तरह, इनपुट के संभावित संयोजन परिमित हैं, लेकिन अगर आप कोड को संशोधित करते हैं, तो विस्तार योग्य है।

इस तरह इस्तेमाल किया:

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

यह आउटपुट:

2011-01-03 16: 11: 16.020 ब्लॉक इनवोकेशन [37096: a0f] ब्लॉक को शीर्ष टेस्ट के साथ लागू किया गया था।

एक लक्ष्य-एक्शन परिदृश्य में उपयोग किया जाता है, जिसमें आपको कुछ ऐसा करने की आवश्यकता होती है:

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

चूंकि लक्ष्य-कार्रवाई प्रणाली में लक्ष्य को बरकरार नहीं रखा गया है, इसलिए आपको तब तक आह्वान सुनिश्चित करने की आवश्यकता होगी जब तक कि नियंत्रण स्वयं के लिए रहता है।

मैं किसी से अधिक विशेषज्ञ से कुछ भी सुनने के लिए इच्छुक हूँ।


आपके पास उस लक्ष्य-एक्शन परिदृश्य में एक स्मृति रिसाव है क्योंकि invocationकभी भी जारी नहीं किया जाता है
23:10 पर उपयोगकर्ता 102008

1

मुझे UIBableViewCell के भीतर एक 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) करने के लिए काम नहीं करता है। यह कोड एआरसी का उपयोग करता है और यह एक ऐप का सरलीकरण है जिसे मैं इसके साथ परीक्षण कर रहा हूं (काम करने के लिए लगता है, कम से कम स्पष्ट रूप से, निश्चित नहीं कि मैं मेमोरी लीक कर रहा हूं)।

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 इसे मार देगा।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.