2011-01-03 24 views

答えて

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カテゴリは、単にこれらのラッパーの1つをインスタンス化し、呼び出されるブロックを与え、ラッパーとそのinvokeBlock:メソッドをターゲットとアクション(通常どおり)として使用するように指示します。
  3. UIControlカテゴリは、UIControlがそのターゲットを保持しないため、関連付けられたオブジェクトを使用してDDBlockActionWrappersの配列を格納します。この配列は、呼び出されるはずのブロックが存在することを保証するための配列です。
  4. は、我々は、オブジェクトが破棄されたとき DDBlockActionWrappersがクリーンアップを取得することを確認する必要があり、私たちは、関連するオブジェクトを削除し、元の deallocコードを呼び出す新しいものと -[UIControl dealloc]をスウィズルの厄介なハックをやっています。トリッキー、トリッキー。 実際には、associated objects are cleaned up automatically during deallocationです。

最後に、このコードはブラウザに入力され、コンパイルされていません。多分それにはいくつかの問題があります。あなたのマイレージは異なる場合があります。

+2

+1関連オブジェクト – user102008

+3

これは、効率的ではないハッシュ検索を意味する関連オブジェクトを使用するよりも、より効率的な方法でこの問題を解決するために、 'objc_implementationWithBlock()'と 'class_addMethod()メソッドルックアップとして)。無関係なパフォーマンスの違いはありますが、これは代替方法です。 – bbum

+0

@bbumあなたは 'imp_implementationWithBlock'を意味しますか? – vikingosegundo

17

いいえ、セレクタとブロックはObjective-Cの中に互換性のある型(実際には、彼らは非常に異なるものです)ではありません。独自のメソッドを記述してセレクタを渡す必要があります。

+11

特に、セレクタは実行するものではありません。オブジェクトに送信するメッセージの名前です(または、この場合のように別のオブジェクトが第3のオブジェクトに送信されるようにします。つまり、[セレクタがここに]メッセージをターゲットに送信するようにコントロールに指示しています)。一方、ブロックは*あなたが実行するものです:あなたはオブジェクトとは無関係に直接ブロックを呼び出します。 –

5

残念ながら、それほど簡単ではありません。

理論的には、targetのクラスにメソッドを動的に追加し、そのメソッドでブロックの内容を実行し、action引数で必要に応じてセレクタを返す関数を定義することは可能です。この関数は、MABlockClosureで使用されている手法を使用することができます。これは、iOSの場合、まだ実験的なlibffiのカスタム実装に依存しています。

あなたは方法として行動を実装する方がよいです。

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

を、あなたは限りコントロール自体が行うようにするために呼び出しオブジェクトの生活を確保する必要があります。

私は誰よりも私より専門家から何かを聞くことに興味があります。

+0

'invocation'が決して解放されないため、ターゲットアクションのシナリオでメモリリークが発生する – user102008

39

ブロックはオブジェクトです。このように、action引数として@selector(invoke)で、target引数として、あなたのブロックを渡します。

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

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

それはまったく動作しないようです... –

+0

テリー:サンプルコードを含むように私の答えを編集しました。 – lemnar

+0

それは面白いです。私は今夜​​似たようなことができるかどうか見てみよう。新しい質問をすることがあります。 –

6

それはUIButtonで@selector引数のためのObjective-Cのブロックを渡すことは可能ですか?

すでに回答がありますが、答えははいですが、いくつかのカテゴリを設定するには少しの作業が必要です。

あなたがオブジェクトとして保存され、呼び出され、タイマーと同様に、このようにして多くのことを行うことができますので、私は等... NSInvocationを使用することをお勧めします...ここで

は私がやったことですが、私はARCを使用しています注意してください。

まず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 

次のブロックに格納する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を使用することができます呼び出し:repeates :)ポイントはNSInvocationにあなたのブロックを回している

が、より汎用性があり、そのように使用することができます。

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

もう一度これは1つの提案に過ぎません。

+0

さらにもう1つは、パブリックメソッドです。 https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSInvocation_Class/Reference/Reference.html#//apple_ref/occ/instm/NSInvocation/invoke – Arvin

0

NSBlockOperation(iOS SDK +5)が動作しません。このコードではARCを使用しており、これをテストしているAppの単純化です(少なくとも明らかにメモリが漏れているかどうかはわかりません)。

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がそれを殺すと思います。

4

GithubのライブラリBlocksKit(これもCocoaPodとして利用可能)には、この機能が組み込まれています。

UIControl + BlocksKit.hのヘッダーファイルを見てください。彼らはDave DeLongのアイデアを実装しているので、そうする必要はありません。いくつかのドキュメントはhereです。

1

私はUITableViewCell内のUIButtonに関連付けられたアクションを持つ必要がありました。私はタグを使用して、すべての異なるセルの各ボタンを追跡することを避けたかったのです。私はこれを達成するための最も直接的な方法はそうのようなボタンにブロック「アクション」を関連付けることだと思った:

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

私の実装は、imp_implementationWithBlockclass_addMethodを言及するため@bbumのおかげもう少し簡略化され、(にもかかわらず広範にテストされていない):

#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, "[email protected]:"); 
    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 
関連する問題