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