4

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 ...を使用しても同じです)、メソッドが返され、ディクショナリの割り当てが解除されます。その後、ブロックを解放する必要があります。しかし、楽器を使用すると、漏れが見られます

リーク

リーク2

リーク3

ブロックに他の参照がないことを「念のために」、メソッドの最後に次の行を追加しました。

[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が発生する可能性があります)

4

2 に答える 2

3

iOS5ブロックARCブリッジキャストのリファレンスについてはこの質問を参照してください。それは、ブロックとARCである悪夢を示しています。

通常、現在のスコープを超えて存在する変数にブロックを割り当てると、コンパイラーはブロックをヒープに自動的にコピーできるようになります。これは、スコープから外れた場合でも、ブロックがぶら下がっていることを意味します。同様に、同じことがブロックパラメータにも当てはまります。コンパイラーは、これらのパラメーターのコピーを作成する必要があることを認識しているため、そうします。

などのクラスの問題NSArrayは、オブジェクトを正しく保持するために通常はオブジェクトをコピーする必要がないことです。通常、それらはオブジェクトのみを保持します。スコープ外に出るオブジェクトは言語の一部である(したがってコピーする)のに対し、オブジェクト内に保持することNSArrayはアプリケーションレベルの操作です。そのため、コンパイラは、ブロックをコピーする必要があるかどうかを判断するのに十分な知識がありません(ブロックは、結局のところ標準のObj-Cオブジェクトであり、必要なのはそれを保持することだけだと考えています)。同様の無駄で、ブロックを保持するプロパティでcopyキーワードを指定する必要があるのはそのためです。プロパティメソッドの自動合成は、ブロックが格納されていることを認識しないため、設定時にそれらをコピーするために微調整を行う必要があります。

これは、ブロックで使用するときにすべてが機能する理由を示し- copyています。コンパイラが実行する必要があることを実行していますが、そうするのに十分賢いわけではありません... Appleは、 ARCへの移行に関するドキュメントでこの手法を推奨しています。よくある質問。

Bootnote: ARCを使用している場合でも、なぜ私が保持することに取り組んでいるのか疑問に思われる場合は、これがARCの内部で行われていることです。メモリ管理モデルは以前と同じですが、名前と規則に基づいてメモリ管理モデルを管理する責任はシステムにありますが、以前はメモリを正しく管理する責任は開発者にありました。ブロックの場合、システムがそれを完全に管理できないため、開発者は時々介入する必要があります。

于 2013-02-05T10:12:15.950 に答える
2

ブロックは、パフォーマンス上の理由からスタック上でその寿命を開始します。スタックが存在するよりも長く存続する必要がある場合は、ヒープにコピーする必要があります。

MRRでは、自分でコピーする必要がありました。ブロックをスタックに渡す(つまり、メソッドからブロックを返す)と、ARCは自動的にそれを実行します。ただし、ブロックをスタックに渡す場合(たとえば、またはに格納するNSMutableDictionary場合NSMutableArray)、自分でコピーする必要があります。

これは、AppleのARCへの移行に関するドキュメントに記載されています。そのドキュメント内で「ブロックはARCでどのように機能するか」を検索してください。

非ARCの例(結論で書いたように)の場合、ブロックのコピー時に保持されるように、ブロックのはcopy解放する前に発生する必要があります。そうしないと、コードが未定義の動作を示し、クラッシュする可能性さえあります。Non-ARCの問題を示すコードを次に示します。aStringaString

NSObject *object = [[NSObject alloc] init];
void (^aBlock)() = ^{
    NSLog(@"%@", object);
};
[object release];
aBlock(); // undefined behavior. Crashes on my iPhone.
于 2013-02-05T11:49:56.073 に答える