メソッドから新しく作成されたオブジェクトを返すには、自動解放プールが必要です。たとえば、次のコードを検討してください。
- (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つはどちらもトップレベルの自動解放プールを管理します。これは、ブロック/タスクを実行する前に作成され、ブロック/タスクを実行すると破棄されます。