ARCで@autoreleasepoolがまだ必要なのはなぜですか?


191

ほとんどの場合、ARC(自動参照カウント)では、Objective-Cオブジェクトでのメモリ管理について考える必要はまったくありません。NSAutoreleasePoolsの作成は許可されなくなりましたが、新しい構文があります。

@autoreleasepool {
    
}

私の質問は、手動でリリース/オートリリースすることになっていないのに、なぜこれが必要になるのでしょうか?


編集:私がすべての回答とコメントから得たものを簡潔に要約すると:

新しい構文:

@autoreleasepool { … } は新しい構文です

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

[pool drain];

さらに重要なことには:

  • ARCはも使用autoreleaseしますrelease
  • そのためには、自動解放プールが必要です。
  • ARCは自動リリースプールを作成しません。しかしながら:
    • すべてのCocoaアプリのメインスレッドには、すでに自動解放プールが含まれています。
  • 次の2つの場合に利用できます@autoreleasepool
    1. セカンダリスレッドで自動解放プールがない場合は、などのリークを防ぐために独自に作成する必要がありますmyRunLoop(…) { @autoreleasepool { … } return success; }
    2. @mattjgallowayが彼の答えで示したように、よりローカルなプールを作成したい場合。

1
3番目の機会もあります。UIKitまたはNSFoundationに関連しない何かを開発するときです。コマンドラインツールなどを使用するもの
Garnik

回答:


214

ARCは保持、リリース、自動リリースを削除しません。必要なものを追加するだけです。したがって、保持の呼び出し、解放の呼び出し、自動解放の呼び出し、および自動解放プールがまだあります。

彼らは新しいクラン3.0コンパイラとARCで作られた他の変化の一つは、彼らが置き換えられていることであるNSAutoReleasePool@autoreleasepoolコンパイラ・ディレクティブ。NSAutoReleasePoolとにかく常に特別な「オブジェクト」であり、それらを使用して、1つを使用する構文がオブジェクトと混同されないようにして、一般的には少しシンプルにしました。

したがって、基本的には、@autoreleasepool心配する自動解放プールがまだあるので必要です。autorelease呼び出しの追加について心配する必要はありません。

自動解放プールの使用例:

- (void)useALoadOfNumbers {
    for (int j = 0; j < 10000; ++j) {
        @autoreleasepool {
            for (int i = 0; i < 10000; ++i) {
                NSNumber *number = [NSNumber numberWithInt:(i+j)];
                NSLog(@"number = %p", number);
            }
        }
    }
}

確かに非常に不自然な例ですが@autoreleasepool、外側のforループの内側にない場合は、外側のforループを回るたびに、1万個ではなく100万個のオブジェクトを後で解放することになります。

更新: この回答も参照してください-https: //stackoverflow.com/a/7950636/1068248-なぜ@autoreleasepoolARCとは何の関係もありません。

更新: ここで何が行われているのかを調べ、ブログに書きました。そこで見てみると、ARCが何をしているか、新しいスタイルが@autoreleasepoolどのようにスコープを導入するのかがコンパイラーによってどのように使用され、保持、リリース、および自動リリースが必要かについての情報が推測されます。


11
それは保持を取り除きません。それはあなたのためにそれらを追加します。参照カウントはまだ行われていますが、それは自動です。したがって、自動参照カウント:-D。
mattjgalloway 2012年

6
では、なぜ@autoreleasepool私にとっても追加されないのですか?自動解放または解放されるものを制御していない場合(ARCがそれを行います)、自動解放プールをセットアップするタイミングをどのように知る必要がありますか?
mk12 2012年

5
ただし、自動解放プールの移動先を制御できます。デフォルトではアプリ全体を包み込んでいるものがありますが、もっと多くしたいかもしれません。
mattjgalloway 2012年

5
良い質問。あなたはただ「知る」必要があります。GC言語で、ガベージコレクターにヒントを追加して、今すぐ収集サイクルを実行する理由に似ていると考えてください。たぶん、クリアできるオブジェクトがたくさんあることを知っているでしょう。一時オブジェクトの束を割り当てるループがあるので、ループの周りにリリースプールを追加すると、良いアイデア。
Graham Perks 2012年

6
ループの例は、自動解放なしで完全に正常に機能します。変数がスコープ外になると、各オブジェクトの割り当てが解除されます。自動解放なしでコードを実行すると、一定量のメモリが消費され、ポインターが再利用されていることが示されます。オブジェクトのdeallocにブレークポイントを設定すると、objc_storeStrongが呼び出されると、ループを通じて毎回呼び出されます。たぶんOSXはここで何かばかげたことをしますが、autoreleasepoolはiOSでは完全に不要です。
グレンメイナード

16

@autoreleasepool何も自動解放しません。自動解放プールが作成されるため、ブロックの終わりに到達すると、ブロックがアクティブなときにARCによって自動解放されたオブジェクトに解放メッセージが送信されます。AppleのAdvanced Memory Managementプログラミングガイドは、次のように説明しています。

自動解放プールブロックの最後に、ブロック内で自動解放メッセージを受信したオブジェクトに解放メッセージが送信されます。オブジェクトは、ブロック内で自動解放メッセージが送信されるたびに解放メッセージを受信します。


1
必ずしも。オブジェクトはreleaseメッセージを受け取りますが、保持カウントが1より大きい場合、オブジェクトは割り当て解除されません。
andybons 2013

@andybons:更新されました。ありがとう。これはARC以前の動作からの変更ですか?
outis 2013

これは誤りです。ARCによって解放されたオブジェクトには、自動解放プールの有無にかかわらず、ARCによって解放されるとすぐに解放メッセージが送信されます。
グレン・メイナード

7

ARCは、ガベージコレクションなどと誤解されることがよくあります。真実は、しばらくしてAppleの人々(llvmとclangプロジェクトのおかげで)がObjective-Cのメモリ管理(すべてのretainsand releasesなど)がコンパイル時に完全に自動化できることを理解したことです。これは、コードを実行する前であっても、コードを読み取るだけです。:)

そのためには、条件が1つだけあります。ルールに従う必要があります。そうでない場合、コンパイラーはコンパイル時にプロセスを自動化できません。だから、私たちがいることを保証するために、決してルールを破るない、我々は明示的に書き込むことが許可されていないreleaseretainなど、これらの呼び出しは、コンパイラによって自動的に私たちのコード内に注入されます。したがって、内部的に我々はまだ持っているautorelease、秒retainrelease、などちょうど私たちはもはやそれを記述する必要はありませんされています。

ARCのAはコンパイル時に自動で行われるため、ガベージコレクションのような実行時よりもはるかに優れています。

@autoreleasepool{...}ルールを破ることがないため、まだあります。必要なときにいつでもプールを作成/排出できます:)。


1
ARCは参照カウントGCであり、JavaScriptやJavaのようなマークアンドスイープGCではありませんが、これは間違いなくガベージコレクションです。これは質問に対応していません-「あなたはできる」は「なぜあなたがすべきなのか」という質問に答えません。すべきではない。
グレンメイナード

3

それは、自動解放されたオブジェクトがスコープ外に出ても安全な場合についてのヒントをコンパイラに提供する必要があるためです。


これを行う必要がある場合の例を教えてください。
mk12 2012年


たとえば、ARCの前は、OpenGLアプリのセカンダリスレッドでCVDisplayLinkを実行していましたが、何も自動再展開していない(またはそのようなライブラリを使用していない)ことがわかっていたため、実行ループに自動解放プールを作成しませんでした。@autoreleasepoolARCが何かを自動解放するかどうかわからないので、追加する必要があるということですか?
mk12 2012年

@ Mk12-いいえ。メインの実行ループを回るたびに排出される自動解放プールが常にあります。自動リリースされたオブジェクトが他の方法で排出される前に確実に排出されるようにする場合にのみ、1つ追加する必要があります。たとえば、次に実行ループをラウンドします。
mattjgalloway 2012年

2
@DougW-私はコンパイラが実際に何をしているのかを調査し、それについてここにブログを書いた-iphone.galloway.me.uk/2012/02/a-look-under-arcs-hood- –-episode-3 /。うまくいけば、コンパイル時と実行時の両方で何が起こっているかを説明します。
mattjgalloway

2

https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.htmlから引用:

自動解放プールのブロックとスレッド

Cocoaアプリケーションの各スレッドは、自動解放プールブロックの独自のスタックを維持します。Foundationのみのプログラムを作成している場合、またはスレッドをデタッチする場合は、独自の自動解放プールブロックを作成する必要があります。

アプリケーションまたはスレッドが長持ちし、潜在的に多くの自動解放オブジェクトを生成する可能性がある場合は、自動解放プールブロックを使用する必要があります(メインスレッドでAppKitやUIKitが行うように)。そうしないと、自動解放されたオブジェクトが蓄積し、メモリフットプリントが増大します。デタッチされたスレッドがCocoa呼び出しを行わない場合、自動解放プールブロックを使用する必要はありません。

注:NSThreadではなくPOSIXスレッドAPIを使用してセカンダリスレッドを作成する場合、Cocoaがマルチスレッドモードでない限り、Cocoaを使用できません。Cocoaは、最初のNSThreadオブジェクトを切り離した後にのみマルチスレッドモードに入ります。セカンダリPOSIXスレッドでCocoaを使用するには、アプリケーションで、少なくとも1つのNSThreadオブジェクトをデタッチする必要があります。これにより、すぐに終了できます。NSThreadクラスのメソッドisMultiThreadedを使用して、Cocoaがマルチスレッドモードであるかどうかをテストできます。

...

自動参照カウント(ARC)では、システムはMRRと同じ参照カウントシステムを使用しますが、コンパイル時に適切なメモリ管理メソッド呼び出しを挿入します。新しいプロジェクトにはARCを使用することを強くお勧めします。ARCを使用する場合、状況によっては役立つ場合がありますが、通常、このドキュメントで説明されている基本的な実装を理解する必要はありません。ARCの詳細については、ARCリリースノートへの移行を参照してください。


2

メソッドから新しく作成されたオブジェクトを返すには、自動解放プールが必要です。たとえば、次のコードを検討してください。

- (NSString *)messageOfTheDay {
    return [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
}

メソッドで作成された文字列の保持カウントは1になります。その保持数とリリースのバランスをとるのは誰ですか?

メソッド自体?不可能です。作成されたオブジェクトを返す必要があるため、戻る前にそれを解放してはなりません。

メソッドの呼び出し元は?呼び出し元は、解放が必要なオブジェクトを取得することを期待していません。メソッド名は、新しいオブジェクトが作成されたことを意味していません。オブジェクトが返され、この返されたオブジェクトが解放を必要とする新しいオブジェクトである可能性がありますが、存在しない既存のものである。メソッドが返す内容は内部状態によって異なる場合があるため、呼び出し側はそのオブジェクトを解放する必要があるかどうかを知ることができず、注意する必要はありません。

呼び出し側が常にすべての返されたオブジェクトを規則によって解放する必要がある場合、新しく作成されなかったすべてのオブジェクトは、メソッドから返す前に常に保持する必要があり、スコープ外になった場合を除いて、呼び出し側によって解放する必要があります。再び返却されます。多くの場合、呼び出し側が常に返されたオブジェクトを解放しない場合、保持カウントの変更を完全に回避できるため、これは多くの場合非常に非効率的です。

そのため、自動解放プールがあるため、最初の方法は実際には

- (NSString *)messageOfTheDay {
    NSString * res = [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
    return [res autorelease];
}

autoreleaseオブジェクトを呼び出すと、オブジェクトは自動解放プールに追加されますが、オブジェクトを自動解放プールに追加すると、どういう意味ですか?まあ、それはあなたのシステムに「私にあなたにそのオブジェクトを解放してほしいが、今ではなく、後でリリースしたい」という意味です。それは解放によってバランスをとる必要がある保持カウントを持っています。そうしないとメモリがリークしますが、私はそれを自分で行うことはできません現在、オブジェクトが現在のスコープを超えて存続する必要があり、呼び出し元もそれを行わないため、これを実行する必要があることを認識していません。プールに追加して、クリーンアップしたらプール、また私のために私のオブジェクトをクリーンアップします。

ARCでは、オブジェクトを保持するタイミング、オブジェクトを解放するタイミング、自動解放プールに追加するタイミングをコンパイラが決定しますが、メモリをリークすることなく、メソッドから新しく作成されたオブジェクトを返すには、自動解放プールの存在が必要です。Appleは生成されたコードに気の利いた最適化を行ったばかりで、実行時に自動解放プールが削除される場合があります。これらの最適化では、呼び出し元と呼び出し先の両方がARCを使用している必要があります(ARCと非ARCの混合は合法であり、正式にサポートされています)。

次のARCコードを検討してください。

// Callee
- (SomeObject *)getSomeObject {
    return [[SomeObject alloc] init];
}

// Caller
SomeObject * obj = [self getSomeObject];
[obj doStuff];

システムが生成するコードは、次のコードのように動作します(つまり、ARCコードと非ARCコードを自由に混在させることができる安全なバージョンです)。

// Callee
- (SomeObject *)getSomeObject {
    return [[[SomeObject alloc] init] autorelease];
}

// Caller
SomeObject * obj = [[self getSomeObject] retain];
[obj doStuff];
[obj release];

(呼び出し元での保持/解放は、防御的な安全保持にすぎないことに注意してください。厳密に必要なわけではありません。コードがなければ、コードは完全に正しくなります)

または、実行時に両方がARCを使用することが検出された場合は、次のコードのように動作します。

// Callee
- (SomeObject *)getSomeObject {
    return [[SomeObject alloc] init];
}

// Caller
SomeObject * obj = [self getSomeObject];
[obj doStuff];
[obj release];

ご覧のように、Appleはatuoreleaseを排除しているため、プールが破壊されたときの遅延オブジェクトの解放と、安全性の維持も行われています。それがどのように可能であり、実際に舞台裏で何が行われているのかについて詳しくは、このブログ投稿をチェックしてください。

さて、実際の質問です。なぜ使用するの@autoreleasepoolでしょうか。

ほとんどの開発者にとって、コードでこの構造を使用する理由は今日1つだけ残っています。それは、メモリフットプリントを適切な場所に小さく保つためです。例えば、このループを考えてみましょう:

for (int i = 0; i < 1000000; i++) {
    // ... code ...
    TempObject * to = [TempObject tempObjectForData:...];
    // ... do something with to ...
}

へのすべての呼び出しがtempObjectForData新しいTempObject自動解放が返さものが。forループは、現在のautoreleasepoolにすべて収集される100万個のこれらの一時オブジェクトを作成し、そのプールが破棄されると、すべての一時オブジェクトも破棄されます。それが発生するまで、メモリにはこれらの一時オブジェクトが100万個あります。

代わりにこのようなコードを書く場合:

for (int i = 0; i < 1000000; i++) @autoreleasepool {
    // ... code ...
    TempObject * to = [TempObject tempObjectForData:...];
    // ... do something with to ...
}

その後、forループが実行されるたびに新しいプールが作成され、各ループ反復の最後に破棄されます。このようにして、ループが100万回実行されているにもかかわらず、最大で1つの一時オブジェクトがいつでもメモリに滞留しています。

NSThreadココア/ UIKitアプリの自動解放プールはメインスレッドのみが自動的に持つため、これまでは、スレッドを管理するとき(たとえばを使用して)に自分で自動解放プールも管理する必要がありました。それでも今日はおそらく最初からスレッドを使用しないため、これは今日のレガシーです。あなたはGCD DispatchQueueまたはを使用しNSOperationQueue、これら2つはどちらもトップレベルの自動解放プールを管理します。これは、ブロック/タスクを実行する前に作成され、ブロック/タスクを実行すると破棄されます。


-4

このトピックについては多くの混乱があるようです(そして、おそらくこれについて今混乱している可能性があり、@ autoreleasepoolをコードの周りに散らす必要があると考えている少なくとも80人)。

プロジェクト(依存関係を含む)がARCのみを使用している場合、@ autoreleasepoolを使用する必要はなく、何の役にも立ちません。ARCは適切なタイミングでオブジェクトの解放を処理します。例えば:

@interface Testing: NSObject
+ (void) test;
@end

@implementation Testing
- (void) dealloc { NSLog(@"dealloc"); }

+ (void) test
{
    while(true) NSLog(@"p = %p", [Testing new]);
}
@end

表示:

p = 0x17696f80
dealloc
p = 0x17570a90
dealloc

自動解放プールが終了するのを待たずに、値がスコープから外れるとすぐに、各Testingオブジェクトの割り当てが解除されます。(NSNumberの例でも同じことが起こります。これにより、deallocを観察できるようになります。) ARCは自動解放を使用しません。

@autoreleasepoolが引き続き許可される理由は、ARCとARC以外のプロジェクトが混在しているためであり、まだ完全にARCに移行されていません。

あなたは非ARCのコードを呼び出す場合は、それを自動解放オブジェクトを返すことがあります。その場合、現在の自動解放プールが終了しないため、上記のループがリークします。これは、コードブロックの周囲に@autoreleasepoolを配置する場所です。

ただし、ARCへの移行を完全に行った場合は、autoreleasepoolを忘れてください。


4
この答えは間違っており、ARCのドキュメントにも反します。コンパイラーが自動解放しないと決定した割り当て方法をたまたま使用しているため、証拠は逸話的です。カスタムクラスの新しい静的初期化子を作成すると、これが機能しないことを非常に簡単に確認できます。この初期化子を作成し、ループで使用します+ (Testing *) testing { return [Testing new] }。その後、deallocは後で呼び出されるまで表示されません。これは、ループの内側を@autoreleasepoolブロックでラップすると修正されます。
ディマ2014

@Dima iOS10で試したところ、オブジェクトのアドレスを出力した直後にdeallocが呼び出されました。+ (Testing *) testing { return [Testing new];} + (void) test { while(true) NSLog(@"p = %p", [self testing]);}
KudoCC 2016年

@KudoCC-私もそうでした、そして私はあなたがしたのと同じ振る舞いを見ました。しかし、[UIImage imageWithData]その方程式に取り掛かると、突然、従来のautorelease動作が見られ始め、@autoreleasepoolピークメモリをある程度のレベルに保つ必要がありました。
Rob

@Rob自分でリンクを追加することはできません。
KudoCC 2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.