web-dev-qa-db-ja.com

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

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

@autoreleasepool {
    …
}

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


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

新しい構文:

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

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

より重要なのは:

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

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

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

基本的に、心配する自動リリースプールがまだあるので、@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);
            }
        }
    }
}

非常に不自然な例ですが、外側のfor- loop内に@autoreleasepoolがない場合、外側のfor- loopをラウンドするたびに10000ではなく、100000000オブジェクトを解放することになります。

更新:この回答も参照してください- https://stackoverflow.com/a/7950636/1068248 -理由@autoreleasepoolはARCとは無関係です。

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

211
mattjgalloway

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

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

15
outis

ARCは、ARCを何らかのガベージコレクションなどと誤解することがよくあります。真実は、しばらくしてAppleの人々(llvmとclangのプロジェクトに感謝​​)がObjective-Cのメモリ管理(すべてretainsreleasesなど)に気づいたということですcompile timeで完全に自動化できます。これは、実行する前であってもコードを読み取るだけです! :)

そのためには、1つの条件のみがあります。 rules に従う必要があります。そうしないと、コンパイラはコンパイル時にプロセスを自動化できません。したがって、neverが規則を破らないようにするために、releaseretainなどを明示的に記述することはできません。これらの呼び出しは、コンパイラによって自動的にコードに挿入されます。したがって、内部的にはまだautoreleases、retainreleaseなどがあります。これ以上記述する必要はありません。

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

@autoreleasepool{...}が残っているのは、それがルールに違反しないためです。必要なときはいつでもプールを自由に作成/排出できます。

7
nacho4d

これは、自動リリースされたオブジェクトが範囲外になることがいつ安全かについてのヒントをコンパイラに提供する必要があるためです。

3
DougW

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リリースノートへの移行」を参照してください。

1
Raunak

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

- (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ループはこれらの一時オブジェクトを100万個作成し、それらはすべて現在の自動解放プールに収集されます。プールが破棄されると、すべての一時オブジェクトも破棄されます。それが起こるまで、これらの一時オブジェクトは100万メモリ内にあります。

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

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

次に、forループが実行されるたびに新しいプールが作成され、各ループの繰り返しの終わりに破棄されます。この方法では、ループが100万回実行されているにもかかわらず、多くても1つの一時オブジェクトがメモリ内でぶらぶらしています。

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

1
Mecki