34

プロトコルを提供し、操作が完了したときにデリゲートメソッドを呼び出すクラスと通信する必要があるとしましょう。

@protocol SomeObjectDelegate

@required
- (void)stuffDone:(id)anObject;
- (void)stuffFailed;

@end

@interface SomeObject : NSObject
{
}
@end

ここで、別のクラスにデリゲートメソッドを実装させることはできますが、インスタンス化や呼び出しなどのstuffDone:場所の近くに記述されたブロックにプロセスをカプセル化することにしました。SomeObjectこれを行う?つまり、ブロックに関するこの有名な記事([コールバックの置換]セクション)を見ると、ある種を受け入れるSomeObjectのメソッドをどのように書くことができcompletionHandler:ますか?

4

3 に答える 3

42

デリゲートオブジェクトを取得するように設計された既存のクラスと通信したいようです。次のような多くのアプローチがあります。

  1. カテゴリを使用して、適切なメソッドのブロックベースのバリアントを追加します。
  2. 派生クラスを使用して、ブロックベースのバリアントを追加します。と
  3. プロトコルを実装し、ブロックを呼び出すクラスを作成します。

これが(3)を行う1つの方法です。まず、SomeObjectが次のようになっていると仮定します。

@protocol SomeObjectDelegate
@required
- (void)stuffDone:(id)anObject;
- (void)stuffFailed;

@end

@interface SomeObject : NSObject
{
}

+ (void) testCallback:(id<SomeObjectDelegate>)delegate;

@end

@implementation SomeObject

+ (void) testCallback:(id<SomeObjectDelegate>)delegate
{
    [delegate stuffDone:[NSNumber numberWithInt:42]];
    [delegate stuffFailed];
}

@end

したがって、テストする方法がいくつかあります。実際のSomeObjectがあります。

次に、プロトコルを実装し、提供されたブロックを呼び出すクラスを定義します。

#import "SomeObject.h"

typedef void (^StuffDoneBlock)(id anObject);
typedef void (^StuffFailedBlock)();

@interface SomeObjectBlockDelegate : NSObject<SomeObjectDelegate>
{
    StuffDoneBlock stuffDoneCallback;
    StuffFailedBlock stuffFailedCallback;
}

- (id) initWithOnDone:(StuffDoneBlock)done andOnFail:(StuffFailedBlock)fail;
- (void)dealloc;

+ (SomeObjectBlockDelegate *) someObjectBlockDelegateWithOnDone:(StuffDoneBlock)done andOnFail:(StuffFailedBlock)fail;

// protocol
- (void)stuffDone:(id)anObject;
- (void)stuffFailed;

@end

このクラスは、渡したブロックを保存し、プロトコルコールバックに応答してそれらを呼び出します。実装は簡単です。

@implementation SomeObjectBlockDelegate

- (id) initWithOnDone:(StuffDoneBlock)done andOnFail:(StuffFailedBlock)fail
{
    if (self = [super init])
    {
        // copy blocks onto heap
        stuffDoneCallback = Block_copy(done);
        stuffFailedCallback = Block_copy(fail);
    }
    return self;
}

- (void)dealloc
{
    Block_release(stuffDoneCallback);
    Block_release(stuffFailedCallback);
    [super dealloc];
}

+ (SomeObjectBlockDelegate *) someObjectBlockDelegateWithOnDone:(StuffDoneBlock)done andOnFail:(StuffFailedBlock)fail
{
    return (SomeObjectBlockDelegate *)[[[SomeObjectBlockDelegate alloc] initWithOnDone:done andOnFail:fail] autorelease];
}

// protocol
- (void)stuffDone:(id)anObject
{
    stuffDoneCallback(anObject);
}

- (void)stuffFailed
{
    stuffFailedCallback();
}

@end

覚えておく必要があるのは、初期化時にブロックをBlock_copy()し、後でBlock_release()することだけです。これは、ブロックがスタックに割り当てられ、オブジェクトが作成中のスタックフレームよりも長持ちする可能性があるためです。Block_copy()は、ヒープ内にコピーを作成します。

これで、すべてのデリゲートベースのメソッドがブロックを渡すことができます。

[SomeObject testCallback:[SomeObjectBlockDelegate
                                  someObjectBlockDelegateWithOnDone:^(id anObject) { NSLog(@"Done: %@", anObject); }
                                  andOnFail:^{ NSLog(@"Failed"); }
                                  ]
]; 

この手法を使用して、任意のプロトコルのブロックをラップできます。

ARC補遺

コメントへの応答:このARCと互換性を持たせるには、Block_copy()直接割り当てを残すための呼び出しを削除するだけです。

stuffDoneCallback = done;
stuffFailedCallback = fail;

メソッドを削除しdeallocます。Blockcopyに変更することもできますcopy。つまりstuffDoneCallback = [done copy];、これは、ARCのドキュメントを読むことで必要になると思われるものです。ただし、割り当てが強力な変数に割り当てられているため、ARCは割り当てられた値を保持し、スタックブロックを保持するとヒープにコピーされます。したがって、生成されたARCコードは、の有無にかかわらず同じ結果を生成しcopyます。

于 2011-01-30T03:15:45.547 に答える
7

あなたはこのようなことをすることができます:

typedef void (^AZCallback)(NSError *);

AZCallback callback = ^(NSError *error) {
  if (error == nil) {
    NSLog(@"succeeded!");
  } else {
    NSLog(@"failed: %@", error);
  }
};

SomeObject *o = [[SomeObject alloc] init];
[o setCallback:callback]; // you *MUST* -copy the block
[o doStuff];
...etc;

次に、内部SomeObjectで、次のことができます。

if ([self hadError]) {
  callback([self error]);
} else {
  callback(nil);
}
于 2011-01-28T08:22:00.287 に答える
1

以下のリンクは、デリゲートを使用したコールバックをブロックに簡単に置き換える方法を説明しています。

例には、UITableview、UIAlertview、およびModalViewControllerが含まれます。

私をクリックしてください

お役に立てれば。

于 2013-08-19T19:33:07.260 に答える