Cocoa と Objective-C による参照カウントを理解する
-
08-06-2019 - |
質問
私は iPhone SDK を使って遊ぶことを目的として、Objective-C と Cocoa について検討し始めたところです。私は C にかなり慣れています malloc
そして free
という概念ですが、Cocoa の参照カウント方式にはかなり混乱しています。一度理解すればとてもエレガントだと言われますが、私はまだ困難を乗り越えていません。
どうやって release
, retain
そして autorelease
それらの使用に関する規則は何ですか?
(または、失敗した場合は何を読んで理解できましたか?)
解決
まずは始めましょう retain
そして release
; autorelease
基本的な概念を理解すれば、実際には単なる特殊なケースにすぎません。
Cocoa では、各オブジェクトが参照された回数を追跡します (具体的には、 NSObject
基本クラスはこれを実装します)。電話することで retain
オブジェクトに対して、その参照カウントを 1 つ増やしたいと伝えていることになります。電話することで release
, 、オブジェクトを手放すことをオブジェクトに伝えると、その参照カウントが減ります。電話をかけたあとの場合 release
, 、参照カウントがゼロになると、そのオブジェクトのメモリがシステムによって解放されます。
これとの基本的な違いは、 malloc
そして free
つまり、オブジェクトが使用していたメモリが解放されているため、オブジェクトはシステムの他の部分がクラッシュすることを心配する必要がありません。全員がルールに従って保持/解放していると仮定すると、あるコードがオブジェクトを保持してから解放しても、そのオブジェクトを参照している他のコードは影響を受けません。
時々混乱するのは、どのような状況で電話をかけるべきかを知ることです。 retain
そして release
. 。私の一般的な経験則では、オブジェクトをある程度の時間保持したい場合 (たとえば、クラス内のメンバー変数の場合)、オブジェクトの参照カウントがそのオブジェクトについて認識していることを確認する必要があります。上で説明したように、オブジェクトの参照カウントは、次の呼び出しによってインクリメントされます。 retain
. 。慣例により、オブジェクトが「init」メソッドで作成されたときにもインクリメントされます (実際には 1 に設定されます)。どちらの場合でも、電話するのは私の責任です。 release
終わったらオブジェクトに。そうしないと、メモリ リークが発生します。
オブジェクト作成の例:
NSString* s = [[NSString alloc] init]; // Ref count is 1
[s retain]; // Ref count is 2 - silly
// to do this after init
[s release]; // Ref count is back to 1
[s release]; // Ref count is 0, object is freed
さてさて autorelease
. 。自動解放は、しばらくしてからこのオブジェクトを解放するようにシステムに指示する便利な (場合によっては必要な) 方法として使用されます。配管の観点から見ると、 autorelease
が呼び出されると、現在のスレッドの NSAutoreleasePool
着信が通知されます。の NSAutoreleasePool
これで、機会が得られると (イベント ループの現在の反復後)、呼び出しできることがわかりました。 release
オブジェクト上で。私たちのプログラマーの観点から見ると、呼び出しを処理します。 release
私たちにとっては、そうする必要はありません(実際、そうすべきではありません)。
注意すべき重要な点は、(繰り返しになりますが) オブジェクトの作成はすべて クラス メソッドは自動解放されたオブジェクトを返します。たとえば、次の例では、変数 "s" の参照カウントは 1 ですが、イベント ループが完了すると破棄されます。
NSString* s = [NSString stringWithString:@"Hello World"];
その文字列を使いたければ、次のように呼び出す必要があります。 retain
明示的に、そして明示的に release
終わったらそれで。
次の (非常に不自然な) コード部分を考えてみると、次のような状況がわかります。 autorelease
が必要です:
- (NSString*)createHelloWorldString
{
NSString* s = [[NSString alloc] initWithString:@"Hello World"];
// Now what? We want to return s, but we've upped its reference count.
// The caller shouldn't be responsible for releasing it, since we're the
// ones that created it. If we call release, however, the reference
// count will hit zero and bad memory will be returned to the caller.
// The answer is to call autorelease before returning the string. By
// explicitly calling autorelease, we pass the responsibility for
// releasing the string on to the thread's NSAutoreleasePool, which will
// happen at some later time. The consequence is that the returned string
// will still be valid for the caller of this function.
return [s autorelease];
}
これらすべてが少し混乱していることは承知していますが、ある時点でピンとくるはずです。参考となる参考資料をいくつか紹介します。
- アップルの紹介 メモリ管理に。
- Mac OS X 用 Cocoa プログラミング (第 4 版), 、アーロン・ヒレガス著 - 素晴らしい例がたくさんある非常によく書かれた本です。チュートリアルのように読めます。
- 本格的に取り組みたい場合は、次の場所に向かうことができます。 ビッグ ナード ランチ. 。これは、上記の本の著者であるアーロン ヒレガスが運営するトレーニング施設です。私は数年前にそこでの Intro to Cocoa コースに参加しましたが、それは素晴らしい学習方法でした。
他のヒント
保持/解放のプロセスを理解していれば、確立された Cocoa プログラマにとっては「当然」の 2 つの黄金律がありますが、残念なことに、初心者にとってこれが明確に説明されていることはほとんどありません。
オブジェクトを返す関数が
alloc
,create
またはcopy
その名前の場合、オブジェクトはあなたのものになります。電話しなければなりません[object release]
それが終わったら。またはCFRelease(object)
, Core-Foundation オブジェクトの場合。名前にこれらの単語が含まれていない場合、そのオブジェクトは他の人のものです。電話しなければなりません
[object retain]
関数の終了後もオブジェクトを保持したい場合。
自分で作成する関数でもこの規則に従うとよいでしょう。
(指摘事項:はい、残念ながら、これらのルールの例外となる API 呼び出しがいくつかありますが、それはまれです)。
デスクトップ用のコードを作成していて、Mac OS X 10.5 をターゲットにできる場合は、少なくとも Objective-C ガベージ コレクションの使用を検討する必要があります。これにより、開発の大部分が本当に簡素化されます。それが、Apple が最初からこれを作成し、適切に実行できるようにすることに全力を注いだ理由です。
GC を使用しない場合のメモリ管理ルールは次のとおりです。
- を使用して新しいオブジェクトを作成すると、
+alloc/+allocWithZone:
,+new
,-copy
または-mutableCopy
または、あなたが-retain
オブジェクトの場合、その所有権を取得することになり、それが確実に送信されるようにする必要があります-release
. - 他の方法でオブジェクトを受け取った場合は、 ない それの所有者であり、そうすべきです ない 確実に送信されるようにする
-release
. - オブジェクトが送信されたことを確認したい場合
-release
自分で送信することも、オブジェクトを送信することもできます-autorelease
そして現在 自動解放プール 送ります-release
(受信ごとに 1 回-autorelease
) プールの水が抜かれたとき。
通常 -autorelease
Cocoa のイベント処理を囲む自動解放プールがあるため、オブジェクトが現在のイベントの間存続するが、その後クリーンアップされることを保証する方法として使用されます。ココアでは、 遠い 呼び出し元自体が解放する必要があるオブジェクトを返すよりも、自動解放されるオブジェクトを呼び出し元に返す方が一般的です。
Objective-C の用途 参照カウント, これは、各オブジェクトに参照カウントがあることを意味します。オブジェクトが作成されると、その参照カウントは「1」になります。簡単に言えば、オブジェクトが参照される (つまり、どこかに保存される) と、そのオブジェクトは「保持」されます。これは、その参照カウントが 1 つ増加することを意味します。オブジェクトが不要になると、そのオブジェクトは「解放」されます。これは、オブジェクトの参照カウントが 1 つ減少することを意味します。
オブジェクトの参照カウントが 0 の場合、オブジェクトは解放されます。これは基本的な参照カウントです。
一部の言語では、参照が自動的に増減されますが、objective-c はそれらの言語の 1 つではありません。したがって、プログラマは保持と解放の責任を負います。
メソッドを記述する一般的な方法は次のとおりです。
id myVar = [someObject someMessage];
.... do something ....;
[myVar release];
return someValue;
コード内で取得したリソースを忘れずに解放する必要があるという問題は、面倒でエラーが発生しやすいものです。Objective-C では、これをはるかに簡単にすることを目的とした別の概念が導入されています。プールの自動解放。自動解放プールは、各スレッドにインストールされる特別なオブジェクトです。NSAutoreleasePool を調べると、これらはかなり単純なクラスです。
オブジェクトが「自動解放」メッセージを受信すると、オブジェクトは現在のスレッドのスタック上にある自動解放プールを探します。このオブジェクトは、将来のある時点 (通常はプール自体が解放されるとき) に「解放」メッセージを送信するオブジェクトとしてリストに追加されます。
上記のコードを次のように書き直すと、より短く読みやすくなります。
id myVar = [[someObject someMessage] autorelease];
... do something ...;
return someValue;
オブジェクトは自動解放されるため、明示的に「release」を呼び出す必要はなくなりました。これは、いくつかの自動解放プールが後でそれを実行してくれることがわかっているためです。
これがお役に立てば幸いです。参照カウントについては、Wikipedia の記事が非常に詳しく説明されています。詳細については、 自動解放プールはここにあります. 。また、Mac OS X 10.5 以降向けにビルドしている場合は、ガベージ コレクションを有効にしてビルドするように Xcode に指示でき、retain/release/autorelease を完全に無視できることにも注意してください。
Joshua (#6591) - Mac OS X 10.5 のガベージ コレクション機能は非常に優れているようですが、iPhone では利用できません (または、アプリを 10.5 より前のバージョンの Mac OS X で実行したい場合)。
また、ライブラリなど再利用される可能性のあるものを作成している場合、GC モードを使用すると、そのコードを使用するすべての人が GC モードを使用するようにロックされるため、私が理解しているように、広く再利用可能なコードを書こうとする人は管理する傾向があります。手動でメモリーします。
これまでと同様に、人々が参考資料を言い換えようとすると、ほぼ例外なく何か間違いを犯したり、説明が不完全になったりします。
Apple は、Cocoa のメモリ管理システムの完全な説明を次のリンクで提供しています。 Cocoa のメモリ管理プログラミング ガイド, の最後には、簡潔だが正確な概要が記載されています。 メモリ管理ルール.
50 ドルを出して Hillegass の本を購入することを検討してみてもよいので、保持/解放の詳細については追加しませんが、アプリケーションの開発の非常に早い段階で Instruments ツールの使用を開始することを強くお勧めします (最初の1つ!)。これを行うには、「実行」->「パフォーマンス ツールで開始」を選択します。まず Leaks から始めます。これは利用可能な多くのインストゥルメントの 1 つにすぎませんが、リリースを忘れたときに役立つでしょう。どれだけの情報が提示されるのか、とても気の遠くなるようなことです。ただし、すぐに立ち上がって作業を開始するには、このチュートリアルを確認してください。
ココアのチュートリアル:計測器のメモリリークを修正する
実際にしようとしています 力 漏洩を防ぐ方法を学ぶには、漏洩を回避する良い方法があるかもしれません。幸運を ;)
return [[s autorelease] release];
自動解放は行います ない オブジェクトを保持します。自動解放は、後で解放されるようにキューに入れるだけです。そこにリリースステートメントを含める必要はありません。
私のいつもの Cocoa メモリ管理記事のコレクション:
iDeveloperTV ネットワークから無料のスクリーンキャストを入手できます
NilObject の答えは良いスタートです。手動メモリ管理に関する補足情報を次に示します (iPhoneでは必須).
あなたが個人的に alloc/init
オブジェクトの場合、参照カウントは 1 になります。不要になった後は、電話するなどしてクリーンアップする責任があります。 [foo release]
または [foo autorelease]
. 。release はオブジェクトをすぐにクリーンアップしますが、autorelease はオブジェクトを autorelease プールに追加し、後で自動的に解放します。
autorelease は主に、問題のオブジェクトを返す必要があるメソッドがある場合に使用します (したがって、手動で解放することはできません。そうでないと、nil オブジェクトが返されます。)でも、それを保持したくありません。
alloc/init を呼び出してオブジェクトを取得しなかった場合にオブジェクトを取得した場合 -- たとえば、次のようになります。
foo = [NSString stringWithString:@"hello"];
しかし、このオブジェクトを保持したい場合は、[foo remember] を呼び出す必要があります。そうしないと、取得される可能性があります autoreleased
そしてあなたはnil参照を保持することになります (上記の場合と同様に stringWithString
例)。不要になったらお電話ください [foo release]
.
上記の回答は、ドキュメントに記載されていることを明確に言い換えています。ほとんどの新人が遭遇する問題は、文書化されていない事件です。例えば:
自動解放:Docsは、「将来のある時点で」リリースをトリガーすると述べています。いつ?!基本的に、コードを終了してシステム イベント ループに戻るまで、オブジェクトが存在することを期待できます。システムは、現在のイベントサイクルの後いつでもオブジェクトを解放してもよい(MAY)。(マットは前にそう言ったと思います。)
静的文字列:
NSString *foo = @"bar";
-- それを保持する必要がありますか、それとも解放する必要がありますか?いいえ。どうでしょうか-(void)getBar { return @"bar"; }
...
NSString *foo = [self getBar]; // still no need to retain or release
作成ルール:あなたがそれを作成した場合、あなたはそれを所有しており、それをリリースすることが期待されています。
一般に、新しい Cocoa プログラマーが混乱するのは、どのルーチンがオブジェクトを返すかを理解していないことです。 retainCount > 0
.
ここからの抜粋です Cocoa のメモリ管理のための非常に単純なルール:
保持数のルール
- 特定のブロック内で、-copy、-alloc、および -retain を使用することは、-release および -autorelease を使用することと同じである必要があります。
- 便利なコンストラクターを使用して作成されたオブジェクト (例:NSString の stringWithString) は自動解放されたものとみなされます。
- -dealloc メソッドを実装して、所有するインスタンス変数を解放します。
最初の箇条書きには次のように書かれています。あなたが電話したなら alloc
(または new fooCopy
)、そのオブジェクトに対して release を呼び出す必要があります。
2 番目の箇条書きには次のように書かれています。便利なコンストラクターを使用する場合 そして、ぶらぶらするオブジェクトが必要です (後で描画するイメージと同様に)、それを保持する (後で解放する) 必要があります。
3 番目は一目瞭然です。