ARCを使用して、このコードがリークしている理由を理解しようとしています。
- (IBAction)block2:(id)sender {
NSMutableString *aString = [[NSMutableString alloc] init];
void (^aBlock)() = ^{
NSMutableString __unused *anotherString = aString;
};
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:aBlock forKey:@"Key"];
}
ご覧のとおり、コレクション内にブロックを配置し(NSMutableDictionaryですが、NSDictionary、NSArray ecc ...を使用しても同じです)、メソッドが返され、ディクショナリの割り当てが解除されます。その後、ブロックを解放する必要があります。しかし、楽器を使用すると、漏れが見られます
ブロックに他の参照がないことを「念のために」、メソッドの最後に次の行を追加しました。
[dict setObject:[NSNull null] forKey:@"Key"];
同じ結果。
私はこの投稿を見つけましたが、答えは別の問題を示しています: NSMutableArrayリーク(ARC)内のブロック
次に、これが魔法です。この行を変更すると、次のようになります。
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:aBlock forKey:@"Key"];
に:
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:[aBlock copy] forKey:@"Key"];
リークが消えます。
非ARCでは、ブロックリテラルの参照を渡す前に、それをコピーする必要があることを知っています(リテラルを宣言すると、スタック上にあるため、関数のスコープ外に渡す前に、ヒープにコピーする必要があります。宣言されています)...しかし、ARCを使用しているので気にする必要はありません。何か兆候はありますか?これは、5.0から6.1までのすべてのバージョンで発生しています。
編集:私はいくつかのテストを行い、私が何か間違ったことをしているのか、それともバグがあるのかを理解しようとしています...
最初に:私は間違った楽器情報を読んでいますか?リークは本物であり、私の間違いではないと思います。この画像を見てください...メソッドを20回実行した後:
2番目:アーク以外の環境で同じことを行おうとするとどうなりますか?これにより、奇妙な動作が追加されます。
非ARC環境での同じ機能:
- (IBAction)block2:(id)sender {
NSMutableString *aString = [[NSMutableString alloc] init];
void (^aBlock)() = ^{
NSMutableString __unused *anotherString = aString;
};
[aString release];
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:[[aBlock copy] autorelease] forKey:@"Key"];
}
以前の非アーク実装では、(文字列ではなく)ブロックに対してのみリークがあります。可変文字列宣言で自動リリースを使用するように実装を変更すると、リークが解決されます!!! 理由がわからず、投稿本編に関係があるのかわからない
// version without leak
- (IBAction)block2:(id)sender {
NSMutableString *aString = [[[NSMutableString alloc] init] autorelease];
void (^aBlock)() = ^{
NSMutableString __unused *anotherString = aString;
};
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:[[aBlock copy] autorelease] forKey:@"Key"];
}
結論:
さまざまな回答とさらに調査した後、私はいくつかのことを理解しました:
1- Appleのドキュメントによると、コレクションにブロックを渡すときは[^{}copy]を使用する必要があります。これは、ARCがコピー自体を追加しないためです。そうしないと、コレクション(配列、ディクショナリ..)はSTACKALLOCATEDOBJECTの保持を送信します-これは何もしません。メソッドが終了すると、ブロックはスコープ外になり、無効になります。それを使用すると、おそらく悪いアクセスを受け取るでしょう。ただし、これは私の場合ではありません。別の問題が発生しています。
2-私が経験している問題は異なります:ブロックが過剰に保持されています(反対の問題->ブロックが存在しない場合でもブロックはまだ生きています)。なんで?私はこれを見つけました:私の例では、私はこのコードを使用しています
void (^aBlock)() = ^{
NSMutableString __unused *anotherString = aString;
};
このコードは、NON-ARCの下で、リテラルブロックへの参照(aBlock)を格納します。ブロックはスタックに割り当てられるため、NSLog(@ "%p"、aBlock)->の場合、スタックメモリアドレスが表示されます。
しかし、これは「奇妙な」ものです(Appleドキュメントには何も示されていません)。ARCおよびNSLog aBlockアドレスで同じコードを使用すると、HEAP上にあることがわかります。このため、動作は異なります(不正なアクセスはありません)
したがって、どちらも正しくないが異なる動作です。
// this causes a leak
- (IBAction)block2:(id)sender {
NSMutableString *aString = [[NSMutableString alloc] init];
void (^aBlock)() = ^{
NSMutableString __unused *anotherString = aString;
};
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:aBlock forKey:@"Key"];
}
// this would cause a bad access trying to retrieve the block from the returned dictionary
- (NSMutableDictionary *)block2:(id)sender {
NSMutableString *aString = [[NSMutableString alloc] init];
return [NSMutableDictionary dictionaryWithObject:^{
NSMutableString __unused *anotherString = aString;
} forKey:@"Key"];
}
3-NON-ARCでの最後のテストについては、リリースが間違った場所にあると思います。copy-autoreleaseを使用してブロックを辞書に追加する前に、文字列を解放しました。ブロックは、ブロック内で参照されている変数を自動的に保持しますが、保持メッセージは、宣言ではなく、コピー時に送信されます。したがって、ブロックをコピーする前にaStringを解放すると、保持カウントが0になり、ブロックは保持メッセージを「ゾンビ」オブジェクトに送信します(予期しない動作では、ブロックのリーク、クラッシュ、ecc eccが発生する可能性があります)