怠け者の為のObjective-Cのメモリ管理

私はメモリ管理関連のの記事を沢山読います.特にiPhone開発初めてからリークなきクラッシュなきアプリの開発に心掛けた。(モバイルデバイスだから下手に扱うとすぐメモり不足の警告が出されたりするから)

そして、最近ツイッターでこの記事(英文)を見つけたので、簡潔で非常に分かりやすくて日本の方に是非読み頂ければという意思でこの記事を翻訳することにした.(簡潔で非常に分かりやすいですよ!)
私はネイティブではないので、おそらく間違っているところがあるだろうが、エッセンスを理解して頂ければと思う.(→最近友人に見てもらって色々と修正をしてくれたので大感謝!)



James Gregoryさんが想像しているよりObj-Cでメモリの管理は簡単

メモリ管理はということばを聞くとスクリプティングする若者たちとJava世界の人が怖がることを私は知っています。でも、実はそんな怖くないんだよ。詳細を言う前にJames Gregoryさんはどう思うかを見てみよう.


iPhoneにないの?!。マジ、アップルよ 一体なんだよ!? ガーベージコレックションがなぜそんなに凄いかと説明しようとするギークが沢山いるから、ここは短く言う。すばらしい理由が基本的に2つある.
一つ目、さらに他の言語のメモリのオーナーシップ(所有権)、いつリリースするベキかとかを理解する暇はないから.
二つ目、開発言語の特徴の中で 開発のプロセスを最も加速するガーベージコレックション(GC)の他に思い当たらないからだだ.
アンドロイドにはあるし、ハードウエアに直接関係しているプログラム以外(つまり、ほど全て)の場合は効率的だ。初代のiPhoneにはなかった理由が分かるかもしれないが 最近のiPhoneにない訳ないだろう。メモリ管理があまりにも大変でこれだけで初心者の開発者にiPhoneよりはアンドロイドを始めることを薦めると思うよ.



えっ!? それは違うよ。メモリ管理について怠け者たちが、大変だと誤解しているんじゃないかなぁ〜。

「さらに他の言語のメモリのオーナーシップ(所有権)、いつリリースするベキかとかを理解する暇はないから」というのは、デベロッパーがめんどくさいな と思っているだけじゃないかなぁ〜。

オブジェクト指向的なメモリ管理の基本は一緒。C++やらObjectPascalでもGCなしの言語なら みんな一緒だ. コンピュータサイエンスの基本の概念でObjective-Cに使うのはほぼささいなことだ.

もし、私は面接官で面接受けている人がそういうこと言ったら、その時点で面接が終了したなぁ、きっと.

しかし、私はGCがあるべきだと思うよ. GCの利点が知らなかったり、分っていないと勘違いしないでください。だから「手動のメモリ管理が大した苦労ではない」というときに あなたはそれを聞いた方がいいと思う。特にObjective-Cでは.

デスクトップのCocoaで開発する(私は結構書くなぁ)ときにCGを使うときがある. プログラムが少し大きくなって、ちょっと調べたり研究したりしているときにCGを使うことが多い。一旦自分が調べたことや試していたことが証明できたらCGを無効にして、全てをリファクタリングするから.

だから、時々GCを使うなら、いつも使えばいいじゃ〜. メモリの管理しなくてもいいから. それにプライベートフレームワークを作って再利用する傾向があるから、それらを動かす為にCGが無効になっているか有効になっているかを見なくてもよくなるし.

Objective-Cのretain/releaseの仕組みは 根本的なメモリ管理とGCの中間的な仕組みだから. C++とかと違って実際にオブジェクトをfreeするわけではない.代わりに「僕は このオブジェクトを使い終えたよ!」と言ってやって あとランライムに任せばいい.




基本の4つのルール

Objective-Cを学ぶときに覚えなければルールが4つだけ.
  • オーナーシップを持つなら、リリースするベキ
  • オーナーシップを持たないなら、リリースしちゃダメ
  • 自分のクラスの実装でdeallocをオバーライドし、オーナーシップを持ったものをリリースするベキ
  • deallocを直接呼び出すベキでない
それは全てだ. 上の2つが最も重要で オーナーシップの基本を次に説明する.

ルール1: オーナーシップを持つとき

Objective-Cでは オブジェクトをallocか、initか、newをしたらオーナーシップを持つころになる.例えば、


簡単だね、これより詳細な説明も思いつかないぐらい。しかし、確認の為に
allocしたらオーナーシップを持つことになる.
copyしたらオーナーシップを持つことになる.
newしたらオーナーシップを持つことになる.(newはalloc/initのショットカットだけ)
〜〜〜Jamesさん、もしまだ読んでいたら 既に4分の1を終えているから、もう少し読んだらソフト掛けるようになるゼ〜〜〜

ルール2:オーナーシップを持っていないとき

これはちょっとややこしいんだ. もっていないときはもっていないんだよ. だから、もしalloc/init/newいずれもしていなかったら 持っていないのだ. 次の例を見てみよう:

そのNSStringのオーナーシップを持っている?いいえ、alloc/copy/newもしなかったから持っていない.
そのNSImageは?はい、持っている.allocしたから.
そして、そのNSData? これも、いいえ. alloc/copy/newどれもしなかったからだ.
ちなみに、本当はこう直すベキ:

〜〜〜そうだね、GCより遥かに大変だわ! 普段ココアの開発者はどういうふうにやってんの?〜〜〜

ルール3と4: dealloc

これは一番大変なところだ. クラスの中にretainしているインスタンス変数があったら、オブジェクトがdeallocされるときにそれらのインスタンス変数をreleaseしないといけない.これを示すのは次の例:

上記の例では、SomeObjectは二つのインスタンス変数がある. thingssomeOtherThingsだ. initメソッドを見て分かるのは 二つをオブジェクトを生成して、それらをインスタンス変数にアサインしていること.
thingsに関しては [NSMutableArray arrayWithObjects]を使っているからretainを呼び出す必要がある. alloc/copy/newいずれもしない. autoreleaseしたオブジェクトを返すコンビニエンスメソッドを使ったから 明示的にretainをしなければいけない. autoreleaseしたオブジェクトとは何かと思っているかもしれないが それはその次に説明する. とりあえず、上記に説明したメモリ管理の基本だけを頭に入れておいてください.
deallocメソッドに 生成したオブジェクトをリリースしているだけです。それ以上はなにもありません.
(ちなみに、コンビニエンスメソッドとは:alloc/init/newで始まらないオブジェクトを生成してくれるメソッド。例: [NSArray array]はコンビニエンスメソッド、[[NSArray alloc] init]はコンビニエンスメソッドではない。ご存知の通り、コンビニエンスというのは便利の意味を持っていて、releaseしなくてもいいから便利という理由で名前持っているらしい. @nacho4d)


プローパーティ

Objective-C 2.0はプローパティーを提供してくれる.その詳細はここでは話さないが プローパーティのライフサイクルの中で混乱が生じやすいところがあるので 少しだけ触れたいと思う. ルールは2つのみ
プローパティーの属性はretainまたはcopyだったら deallocnilをセットするベキ.
initメソッドなどでプローパーティを初期化(init)しているならば autoreleaseにするベキ.
例を見てみよう.


titleプローパーティに関して NSStringのコンビニエンスメソッドを使って生成しているからautoreleaseしたオブジェクトが返ってきて 何もする必要がありません.
subtitleプローパティーに関して 私たち、自分で、オブジェクトを新しく(alloc/initを使って)生成しているのでautoreleaseをする必要があります.
理由はこれから説明します. alloc/initを使ってretainカウントが1になり、subtitleにアサインすると一個増えるので2になってしまう. autorelease無しで行うとリテインカウントが決して1に戻らなず メモリリークが発生してしまう. だからautoreleaseを入れることで リテインカウントが1個減らしている.

オートリリースとは?

実は完全に嘘をついた、完全に.これは実は結構複雑な仕組みで その複雑さはオートリリースプール(Autorelease pool)という形で来ている。それは一体なんだ? オートリリースプールはreleaseメッセージを送られる対象オブジェクトのリストを持っている。いつ送られるかというとプール
は破壊される(destroyed)また排出させた(drained)ときです。 しかし、それはいつ起きるかを気にしなくても大丈夫です. 正しくautoreleaseメッセージを送っておければ オブジェクトがちゃんとリリースされるからだ.

さて、基本のルールに従って行えば オブジェクトを返すメソッドについての疑問がある筈だなぁ。このオブジェクトのオーナーシップを誰が持っているの?ここはオートリリースプールの出番だ.
例えば:

そのNSImageのオーナーシップを誰が持っている? ここはねぇ、この例では、メッセージを送ったオブジェクトはオーナーシップを持つことになる.よって送られた側にreleaseメッセージを送る必要がある。しかし、これは悪い例です.正しいのは次のよう:

Autoreleaseメッセージが最後にあることをに着目しよう.autoreleaseをすると そのオブジェクトがオートリリースプールに追加され poolが破壊されるときに releaseメッセージが送られます.
(なぜ一個上のが悪い例でこれは良い例かという説明を次にします。
実はメモリ管理の観点から見ると両方とも問題はありません。後でreleaseするか、さきにautoreleaseをするかどっちも一緒です。
しかし、前者の方がCocoaのコンベンションの一つに違反しています。 
私は上に書いたコンビニエンスメソッドの定義によると、alloc/init/newで始まるメソッドを送るとオーナーシップを持つことになります。
逆に言えば、そうで始まらないメソッドを送るとオーナーシップを持たないことになりますね。そして、getAnImageは何かの形でオブジェクトを生成しています。かつ、alloc/init/newで始まらないから コンビニエンスメソッドですよね?こう言ったコンビニエンスメソッドが CocoaのコンベンションによるとAutoreleasedオブジェクトを返す筈だよね?、だから コンベンションに従って書くようにしましょう. @nacho4d)

オートリリースプールの中にさらに別のオートリリースプールはあってもいいので、大量の一時的なオブジェクトを生成するときに便利. そして、もしマルチスレッドの処理をしていれば、新しいスレッド用のオートリリースプールを作る必要があります.

まとめ

思ったより、そんな怖くないし、複雑ではなかろう. 勿論GCを全然簡単だが、例えGCがあっても手動メモリ管理に向いている処理が沢山があって、手動メモリ管理がメーディアやゲームなどのアプリケーションでもっとも使われている。
このようなアプリケーションにはメモリ管理は極めて重要な役割で、知らないとうまくいかない場合が沢山あるよ. ありがたいことに CocoaとObjective-Cを使うとそんなに苦労しなくて済む.

参考文献

実はアップルのドキュメンテーションで十分な筈だが、すばらしい記事が沢山かあって...
などなど...

0 comments :

This work is licensed under BSD Zero Clause License | nacho4d ®