13

ブロックを引数として受け入れるメソッドを作成する場合、実行する前にブロックをヒープにコピーするなど、特別なことを行う必要がありますか?たとえば、次の方法がある場合:

- (void)testWithBlock:(void (^)(NSString *))block {
    NSString *testString = @"Test";
    block(testString);
}

blockそれを呼び出す前、またはメソッドを入力するときに何かをする必要がありますか?または、上記は渡されたブロックを使用する正しい方法ですか?また、メソッドを呼び出す次の方法は正しいですか、それともブロックを渡す前に何かを行う必要がありますか?

[object testWithBlock:^(NSString *test){
    NSLog(@"[%@]", test);
}];

ブロックをどこにコピーする必要がありますかそして、私がARCを使用していなかったとしたら、これはどのように異なっていただろうか?

4

4 に答える 4

19

メソッドパラメータとしてブロックを受け取った場合、そのブロックはスタック上に作成されたオリジナルであるか、コピー(ヒープ上のブロック)である可能性があります。私の知る限り、わかりません。したがって、一般的な経験則では、ブロックを受信するメソッド内でブロックを実行する場合は、ブロックをコピーする必要はありません。そのブロックを別のメソッドに渡す場合(すぐに実行される場合とされない場合があります)、コピーする必要もありません(受信側のメソッドは、ブロックを保持する場合はコピーする必要があります)。ただし、後で実行するためにブロックをどこかに保存する場合は、ブロックをコピーする必要があります。多くの人が使用する主な例は、インスタンス変数として保持されているある種の完了ブロックです。

typedef void (^IDBlock) (id);
@implementation MyClass{
    IDBlock _completionBlock;
}

ただし、NSArrayやNSDictionaryなど、あらゆる種類のコレクションクラスに追加する場合は、コピーする必要もあります。そうしないと、後でブロックを実行しようとしたときにエラー(EXC_BAD_ACCESSの可能性が高い)またはデータ破損が発生する可能性があります。

ブロックを実行するときは、最初にブロックがであるかどうかをテストすることが重要ですnil。Objective-cを使用するnilと、ブロックメソッドパラメータに渡すことができます。そのブロックがnilの場合、実行しようとするとEXC_BAD_ACCESSが発生します。幸いなことに、これは簡単です。あなたの例では、次のように記述します。

- (void)testWithBlock:(void (^)(NSString *))block {
    NSString *testString = @"Test";
    if (block) block(testString);
}

ブロックのコピーにはパフォーマンスに関する考慮事項があります。スタック上にブロックを作成する場合と比較して、ブロックをヒープにコピーするのは簡単ではありません。これは一般的に大きな問題ではありませんが、ブロックを繰り返し使用する場合、またはブロックの束を繰り返し使用して実行ごとにコピーする場合は、パフォーマンスが低下します。したがって、メソッド- (void)testWithBlock:(void (^)(NSString *))block;がある種のループにある場合、そのブロックをコピーする必要がなければ、そのブロックをコピーするとパフォーマンスが低下する可能性があります。

ブロックをコピーする必要があるもう1つの場所は、そのブロック自体を呼び出す場合です(ブロック再帰)。これはそれほど一般的ではありませんが、これを行う場合はブロックをコピーする必要があります。SOに関する私の質問/回答をここで参照してください:Objective-Cの再帰ブロック

最後に、ブロックを保存する場合は、保持サイクルの作成に十分注意する必要があります。ブロックは、渡されたオブジェクトを保持し、そのオブジェクトがインスタンス変数である場合は、インスタンス変数のクラス(自己)を保持します。私は個人的にブロックが大好きで、いつも使っています。しかし、AppleがUIKitクラスにブロックを使用/保存せず、代わりにターゲット/アクションまたはデリゲートパターンのいずれかに固執するのには理由があります。あなた(ブロックを作成するクラス)がブロックを受信/コピー/保存しているクラスを保持していて、そのブロックで自分自身または任意のクラスインスタンス変数を参照している場合、保持サイクルを作成しました(classA-> classB- >ブロック->クラスA)。これは非常に簡単で、何度もやりすぎました。また、「リーク」楽器ではそれをキャッチしません。これを回避する方法は簡単です。一時的なものを作成するだけです。__weak変数(ARCの場合)または__block変数(非ARCの場合)であり、ブロックはその変数を保持しません。したがって、たとえば、「オブジェクト」がブロックをコピー/保存する場合、次は保持サイクルになります。

[object testWithBlock:^(NSString *test){
    _iVar = test;
    NSLog(@"[%@]", test);
}];

ただし、これを修正するには(ARCを使用):

__weak IVarClass *iVar = _iVar;
[object testWithBlock:^(NSString *test){
    iVar = test;
    NSLog(@"[%@]", test);
}];

次のようなこともできます。

__weak ClassOfSelf _self = self;
[object testWithBlock:^(NSString *test){
    _self->_iVar = test;
    NSLog(@"[%@]", test);
}];

多くの人は上記が壊れやすいと考えているので気に入らないことに注意してください。しかし、それは変数にアクセスするための有効な方法です。 更新-現在のコンパイラは、「->」を使用して変数に直接アクセスしようとすると警告するようになりました。このため(および安全上の理由から)、アクセスする変数のプロパティを作成するのが最善です。したがって、代わりに_self->_iVar = test;次を使用します_self.iVar = test;

更新(詳細)

一般に、ブロックを受け取るメソッドは、呼び出し元ではなく、ブロックをコピーする必要があるかどうかを判断する責任があると考えるのが最善です。これは、受信メソッドが、ブロックを存続させる必要がある期間、またはブロックをコピーする必要があるかどうかを知っている唯一のメソッドである可能性があるためです。あなた(プログラマーとして)は、呼び出しを書くときに明らかにこの情報を知っていますが、呼び出し元と受信者を別々のオブジェクトで精神的に考える場合、呼び出し元は受信者にブロックを与え、それで完了します。したがって、ブロックがなくなった後、ブロックで何が行われるかを知る必要はありません。反対に、それは」呼び出し元がすでにブロックをコピーしている可能性がありますが(おそらくブロックを保存していて、別のメソッドに渡している可能性があります)、受信者(ブロックを保存しようとしている)はブロックをコピーする必要があります(ブロックがブロックされている場合でも)すでにコピーされているように)。受信者は、ブロックがすでにコピーされていることを知ることができず、受信したブロックの中にはコピーされているものとコピーされていないものがあります。したがって、受信者は常に、保持する予定のブロックをコピーする必要がありますか?わかる?これは本質的に、優れたオブジェクト指向の設計手法です。基本的に、情報を持っている人は誰でもそれを処理する責任があります。したがって、受信者は常に、保持する予定のブロックをコピーする必要がありますか?わかる?これは本質的に、優れたオブジェクト指向の設計手法です。基本的に、情報を持っている人は誰でもそれを処理する責任があります。したがって、受信者は常に、保持する予定のブロックをコピーする必要がありますか?わかる?これは本質的に、優れたオブジェクト指向の設計手法です。基本的に、情報を持っている人は誰でもそれを処理する責任があります。

ブロックは、マルチスレッドを簡単に有効にするためにAppleのGCD(Grand Central Dispatch)で広く使用されています。一般に、GCDにディスパッチするときに、ブロックをコピーする必要はありません。奇妙なことに、これは少し直感に反します(考えてみれば)。ブロックを非同期にディスパッチすると、ブロックが実行される前にブロックが作成されたメソッドが返されることがよくあります。これは、通常、ブロックが期限切れになるためです。スタックオブジェクト。GCDがブロックをスタックにコピーするとは思いません(どこかで読んだのですが、もう一度見つけることができませんでした)。代わりに、別のスレッドに配置することでスレッドの寿命が延びると思います。

Mike Ashには、ブロック、GCD、およびARCに関する広範な記事があります。

于 2012-05-06T20:09:51.193 に答える
7

これはすべてよさそうだ。ただし、ブロックパラメータを再確認することをお勧めします。

@property id myObject;
@property (copy) void (^myBlock)(NSString *);

...。

- (void)testWithBlock: (void (^)(NSString *))block
{
    NSString *testString = @"Test";
    if (block)
    {
        block(test);
        myObject = Block_copy(block);
        myBlock = block;
    }
}

..。

[object testWithBlock: ^(NSString *test)
{
    NSLog(@"[%@]", test);
}];

大丈夫なはずです。そして、彼らは段階的に廃止しようとしていると私は信じていますがBlock_copy()、まだです。

于 2012-05-03T02:20:38.900 に答える
4

ブロックプログラミングトピックガイドが「ブロックのコピー」で述べているように:

通常、ブロックをコピー(または保持)する必要はありません。宣言されたスコープの破棄後にブロックが使用されることを期待する場合にのみ、コピーを作成する必要があります。

説明している場合、基本的に、ブロックは、intまたは他のプリミティブ型であるかのように、メソッドの単なるパラメーターであると考えることができます。メソッドが呼び出されると、スタック上のスペースがメソッドパラメーターに割り当てられるため、ブロックは、メソッドの実行全体を通してスタック上に存在します(他のすべてのパラメーターと同様)。メソッドの戻り時にスタックフレームがスタックの最上位からポップされると、ブロックに割り当てられたスタックメモリの割り当てが解除されます。したがって、メソッドの実行中、ブロックは存続していることが保証されているため、ここで処理するメモリ管理はありません(ARCと非ARCの両方の場合)。言い換えれば、あなたのコードは大丈夫です。メソッド内のブロックを呼び出すだけです。

引用されたテキストが示すように、ブロックを明示的にコピーする必要があるのは、ブロックが作成されたスコープの外部から(この場合、メソッドのスタックフレームの存続期間を超えて)アクセスできるようにする場合のみです。例として、Webからデータをフェッチし、フェッチが完了するとコードのブロックを実行するメソッドが必要だとします。メソッドシグネチャは次のようになります。

- (void)getDataFromURL:(NSURL *)url completionHandler:(void(^)(void))completionHandler;

データのフェッチは非同期で行われるため、ブロックを保持し(クラスのプロパティにある可能性が高い)、データが完全にフェッチされたらブロックを実行する必要があります。この場合、実装は次のようになります。

@interface MyClass

@property (nonatomic, copy) void(^dataCompletion)(NSData *);

@end



@implementation MyClass
@synthesize dataCompletion = _dataCompletion;

- (void)getDataFromURL:(NSURL *)url completionHandler:(void(^)(NSData *fetchedData))completionHandler {
    self.dataCompletion = completionHandler;
    [self fetchDataFromURL:url]; 
}

- (void)fetchDataFromURL:(NSURL *)url {
    // Data fetch starts here 
}

- (void)finishedFetchingData:(NSData *)fetchedData {
    // Called when the data is done being fetched
    self.dataCompletion(fetchedData)
    self.dataCompletion = nil; 
}

この例では、copyセマンティックを持つプロパティを使用するBlock_copy()と、ブロックに対してが実行され、ヒープにコピーされます。これは行で発生しますself.dataCompletion = completionHandler。したがって、ブロックは-getDataFromURL:completionHandler:メソッドのスタックフレームからヒープに移動され、メソッドの後半で呼び出すことができますfinishedFetchingData:。後者の方法では、行self.dataCompletion = nilはプロパティを無効にBlock_release()し、格納されたブロックにを送信して、割り当てを解除します。

このようにプロパティを使用すると、基本的にすべてのブロックメモリ管理が処理され(単なる)ではなくcopy(またはstrong)プロパティでありretain、ARC以外の場合とARCの場合の両方で機能するため便利です。代わりに、生のインスタンス変数を使用してブロックを格納し、非ARC環境で作業している場合、ブロックをより長く保持したい場合は、、、および自分自身をすべての適切な場所でBlock_copy()呼び出すBlock_retain()必要があります。Block_release()パラメータとして渡されたメソッドの存続期間。プロパティの代わりにivarを使用して記述された上記の同じコードは、次のようになります。

@interface MyClass {
    void(^dataCompletion)(NSData *);
}

@end



@implementation MyClass

- (void)getDataFromURL:(NSURL *)url completionHandler:(void(^)(NSData *fetchedData))completionHandler {
    dataCompletion = Block_copy(completionHandler);
    [self fetchDataFromURL:url]; 
}

- (void)fetchDataFromURL:(NSURL *)url {
    // Data fetch starts here 
}

- (void)finishedFetchingData:(NSData *)fetchedData {
    // Called when the data is done being fetched
    dataCompletion(fetchedData)
    Block_release(dataCompletion);
    dataCompletion = nil;
}
于 2012-05-05T04:34:25.330 に答える
0

あなたは2種類のブロックがあることを知っています:

  1. スタックに格納されているブロック、明示的に^ {...}として記述したブロックは、通常のスタック変数と同様に、関数が返されるとすぐに消えます。所属する関数が戻った後でスタックブロックを呼び出すと、悪いことが起こります。

  2. ヒープ内のブロック、別のブロックをコピーしたときに取得するブロック、通常のオブジェクトと同じように、他のオブジェクトがそれらへの参照を保持している限り存続するブロック。

ブロックをコピーする唯一の理由は、スタックブロック(明示的なローカルブロック^ {...}、または起源がわからないメソッド引数)である、またはスタックブロックである可能性のあるブロックが与えられた場合です。スタックブロックの限られた1つからその寿命を延ばしたい、そしてコンパイラがまだその仕事をしていないこと。

考えてみてください。インスタンス変数にブロックを保持します。

NSArrayなどのコレクションにブロックを追加します。

これらは、ブロックがすでにヒープブロックであるかどうかわからない場合に、ブロックをコピーする必要がある場合の一般的な例です。

ブロックが別のブロックで呼び出されたときに、コンパイラが自動的にそれを行うことに注意してください。

于 2012-05-05T06:03:28.810 に答える