2009/03/06 カテゴリ : Tech  iPhone 

IBを使わずに作るiPhoneアプリ作成入門:第3回

こんにちは、亀本です。

やっとこさの第3回です。また少し期間が空いてしまいました。

第1回:http://blog.asial.co.jp/502
第2回:http://blog.asial.co.jp/531

今回から、前回予告していたメモリ管理周りについて、iPhoneではどのように扱っているかを説明します。
そのために、今回はまずiPhone/Objective-Cでの基本的なメモリ管理の仕組みについて説明します。
また、次回もメモリ管理周りのメソッド説明とかになります。
このあたり、全体的に教科書っぽい話で、コードの説明はあまり出ません。ごたくが長くなるので、読み疲れ内容に細切れに出していこうかと思っています。

Objective-Cにおける、メモリの確保と開放

普段PHPなどのようなLL言語を扱っていると、なかなかメモリの確保と開放という話は縁がありません。
そういった内容は、PHPなどの言語自体がよしなにやってくれているので気にしなくて大丈夫になっています。

Objective-Cではこのあたりをきちんと扱わなければいけません。
とはいえ、Cのmalloc/freeみたいなものを全部自前でやらなければいけないわけではなく
・「メモリを確保するよ!」
・「ポインタを保持するよ!」
・「解放するよ!」
ということを明示的に行っていく必要がある、ということです。

これまで説明してきたコードでは、メモリの確保は行っていても、解放が適切に行われていませんでした。
メモリの解放忘れがあると、プログラムが終了しても確保されたまま利用できない領域が生まれ、使用メモリ量を圧迫する「メモリリーク」と呼ばれる状態になってしまいます。

そのため、これからさらに開発を続けていく上では、メモリの確保と開放をきちんと意識して進めていく必要があります。

それでは、実際にiPhone開発で使うメモリの確保と開放について話を進めていきましょう。
まずは、Objective-Cで採用されているメモリ管理方式である「リファレンスカウンタ」の簡単な考え方から説明していきます。

リファレンスカウンタ

オブジェクトは単純なリテラルなどに比べて複雑な要素を持つため、オブジェクトを生成するにはそれなりに大きなメモリ領域を必要とします。
そのため、オブジェクトを変数に代入する、というような動作を行う場合、これが毎回コピーされてしまうとメモリ使用量もそれだけ多くなってしまいます。
しかし実際には、そんなに毎回コピーを行う必要はなく、必要な時だけ明示的にコピーできればそれで十分、という事がほとんどです。

この問題の解消のため、Objective-Cでは、オブジェクトの変数への代入の際には、オブジェクトそのものではなくポインタを渡します。
こうすることで、1つのオブジェクトを複数の変数で共有する事ができ、無駄な処理やメモリ消費を抑制できます。
(必ずしも、本当にこういう理由が主なのかは判りませんが。。。理由の一因ではあります)

しかし、このやり方をとった時に問題になるのが、メモリ解放の判断です。
複数の変数から同じオブジェクトを見ていると、あちこちから呼び出しがかかることになります。
そんな中で、どれか一つの変数が勝手にオブジェクトを破棄してしまうと、他の変数から呼び出しがかかった時におかしな事になります。
  1. 呼び出そうと思ったら、もうそこにはオブジェクトがなかった。。。何を言っているかわからねーと思うが(ry
みたいなエラーになります。(こういう状態になったポインタを「ぶらさがりポインタ」と呼んだりもします。)

・・・ということは。
誰かが勝手にオブジェクトを破棄できてはいけないわけです。
ちゃんと「誰も必要としていない」という事が判って初めて、破棄/解放してよいのです。

・・・ということはということは。
誰がこのオブジェクトを保持しているか、を、何かしらの方法でプログラム側が知っておく必要があります。
少なくとも「まだ必要としている変数がいるかどうか」が把握できる仕組みと、「誰も必要としなくなったときに自動的に破棄する」という仕組みが必要です。
そして、この仕組みを上手い事やってくれる手法が、「リファレンスカウンタ」方式です。

リファレンスカウンタとは、オブジェクトのポインタが別の変数にコピーされた数をカウントする方法です。
簡単な挙動の説明をします。

まず、ある変数Aが最初にオブジェクトを生成して格納したとします。するとまず、リファレンスカウンタが1になります。
さらに、変数Aから変数Bにオブジェクト(のポインタ)が代入されたとします。そうすると、ここで参照している変数が2つになるため、リファレンスカウンタが2になります。

反対に、変数Bが解放されたとすると、参照している変数がAだけになるため、リファレンスカウンタは1減って1になります。
さらに変数Aも解放されると、参照が0個になって、リファレンスカウンタも0となります。
そして、カウンタが0になったということは「誰からも必要とされなくなった」ということになり、この時点でオブジェクトが破棄されます。

このように、オブジェクトのポインタを保持している変数の数をカウントしていくことでオブジェクトの要不要を知り、オブジェクトの解放を管理するのが、リファレンスカウンタ方式です。
Objective-Cではこのリファレンスカウンタを採用していますが、単にこのカウント増減を自動的に行うのではなく、明示的にメソッドを呼んでカウントを増加させたり減少させたりするように実装されています。


さて、それではリファレンスカウンタ方式についてざっくり理解したところで、基本的なメモリ確保・解放などに使うメソッドを紹介していきましょう。

メモリ管理を行うメソッド群

ここでは、メモリ管理の基本的なメソッド、alloc/dealloc/release/retainを紹介します。

○alloc
メモリの確保を行うメソッドです。以前も説明しましたね。
これは、すべてのオブジェクトに備わっているメソッドで、上書きしてしまうと大変な目にあうのでやめましょう。

  1.   NSObject *obj = [[NSObjject alloc] init];
のように使います。
このメソッドはオブジェクト生成とメモリ領域の動的確保を行うだけで、そのオブジェクトの初期化は行われません。
そのため、allocはinitなどのイニシャライザと併せて利用されるのが通常で、逆にalloc - initをしないとオブジェクトを利用できません。

(ただし、中にはallocでのオブジェクト生成も一括で行ってくれるような、「コンビニエンスコンストラクタ」と呼ばれる便利なメソッドも存在します。
たとえばNSStringクラスのstringWithHogehoge....というようなメソッドは、内部的にallocと初期化、および後述するautoreleaseを同時に行ってくれる、便利な糖衣構文です。)

allocで生成されたオブジェクトは、その最初の生成時点ですでに、リファレンスカウンタを+1しています。
そのため、これによって生成されたオブジェクトを格納した変数は、必ず使い終わった後にrelease、もしくはautoreleaseをする必要があります。(releaseはカウンタを1つ減らすメソッドで、詳細は後述。autoreleaseは次回説明します。)

○dealloc
オブジェクトの破棄とメモリの解放を行います。
機能的にはallocの対になるものに感じられますが、これを明示的に呼び出すことは普通ありません。というよりも、呼び出してはいけません。自動的に呼び出されるものです。
deallocは、そのオブジェクトが他から参照されているかどうかに関わらず、呼び出された時点でそのオブジェクトを破棄・メモリを解放します。
不適切なタイミングでdeallocされてしまうと、ポインタの指定先がないぶらさがりポインタを生むことになり、エラーの元となります。

では、deallocはどのような時に呼び出されるのでしょうか?それはすなわち、リファレンスカウンタが0になった場合です。
deallocは、オブジェクトに対する参照がなくなった時に自動的に呼び出され、オブジェクトを破棄します。

このような事情があるためdeallocの呼び出しは気にする必要がありませんが、実装はオーバーライドする必要があります。
なぜなら、deallocはオブジェクト破棄時に呼び出されるため、オブジェクトが消える前の後処理をこのメソッド内で行う必要があるからです。
そのため、新しいオブジェクトを実装したらほぼ必ず
  1. - (void) dealloc {
  2.   // ここでメンバ変数のreleaseなどをする
  3.   [super dealloc];
  4. }
という実装を行うことになります。
これを忘れると、オブジェクトのメンバ変数に保持されていた別のオブジェクトが正しくrelease/破棄されずに、メモリリークの原因になりますので、必ずやるようにしましょう。

○retain
オブジェクトを「保持(retain)する」という事を明示的に行うメソッドです。
retainを実行することで、そのオブジェクトのリファレンスカウンタが+1され、他の場所でreleaseが呼ばれカウンタが減った際にも、オブジェクトが破棄されずに済むようになります。
  1.   [obj retain];

○release
オブジェクトを「手放す(release)」という事を明示的に行います。
releaseをするとリファレンスカウンタが-1されます。
そして、releaseによってリファレンスカウンタが0になると、そのオブジェクトのdeallocメソッドが呼び出され、オブジェクトが破棄される、という仕組みになっています。
そのため、allocであれretainであれ、生成なり保持なりを行ったオブジェクトに対しては、使い終わったら常にreleaseを実行するようにすると、必ず最後にはリファレンスカウントが0になって、メモリリークが防げるプログラムが作れる、という事になります。
使い終わったらrelease。というのは、しっかりと意識しましょう。
  1.   [obj release];

とはいえ。この「使い終わったらrelease」というのが簡単そうで意外と難しかったりします。
returnするオブジェクトはどうするの?とか、コードが煩雑になるよ~とか、どこでretainしてどこでreleaseしてるのかよくわかんない><。。とか色々面倒な面があります。
その辺をきっちりやったり、すっきりさせるために「オーナーシップ・ポリシー」と言う考え方と、「autorelease」という機能が活用できます。これらについては、次回説明していきます。

まとめ
今回は割とあっさり、リファレンスカウンタの簡単な説明と、基本となる各種メソッドを紹介しました。
あっさり過ぎてわからなかったらどうしよう、とも思いつつ。。。w
なんか結局コードの説明がなかったですね。。w

次回は、メモリ管理においてさらに重要なオーナーシップポリシーやAutoReleasePool、といった話題を続けていきます。

コメント

    • 迷子
    • 2009年03月14日 00:19
    • 初めまして、ここ最近iPhoneアプリを作ろうと挑んでます。
      Java、C++の入門書程度の知識がある程度ですが...

      早速ですが、最終的なソースコードがどのような状態になるのかのせることは可能ですか?
      また、各メソッドがコールされる流れ、処理の流れがいまいちつかめないのですが、、下記順番ではあっていないですよね?
      1.main関数スタート
      2.UIApplicationMainメソッド
      3.sampleProjAppDelegateクラスのapplicationDidFinishLaunchingメソッドが呼ばれる
      4.applicationDidFinishLaunchingメソッド内で、SimpleTableViewControllerクラスがインスタンス化
      5.インスタンスで、SimpleTableViewControllerクラスのapplicationDidFinishLaunchingメソッドが呼ばれる
      ?tableViewメソッドはいつ呼ばれるのでしょうか?
      また、書籍などみてもメソッドが定義されているんですが、
      そのメソッドをコールしている箇所がなかったりと説明がなかったりで。。。
    • 迷子
    • 2009年03月14日 00:22
    • すみません。↑のコメントですが、
      第2回目に対するものです。
    • かめもと
    • 2009年03月15日 04:02
    • > 迷子さん
      最終的な物を見たい、という類の話は色々転がっているサンプルコードを色々見ていただいた方が、たぶんずっと良いテキストになると思います。
      一応、この連載はコードを組み上げながら順番に説明していく形を想定しています。

      メソッドのコール順ですが、
      > 5.インスタンスで、SimpleTableViewControllerクラスのapplicationDidFinishLaunchingメソッドが呼ばれる
      の部分がおかしいですね。
      あくまでもapplicationDidFinishLaunchingはUIApplicationクラスの中のフックメソッドなので、UITableViewControllerクラスは仕組みが違います。

      インスタンス化された後は、特にSimpleTableViewControllerのメソッドは明示的にコールしていません。実際には、以下のような流れで暗黙的に呼び出されています。

      [window makeKeyAndVisible];
      がコールされると、windowのsubviewがレンダリングされます。
      サンプルでは、windowにSimpleTableViewControllerのviewがaddSubviewされているので、このviewがレンダリングされることになります。
      具体的には、SimpleTableViewControllerのloadViewメソッドが呼び出されることになります。
      このloadViewメソッドが、実際の表示部分を構築します。

      > ?tableViewメソッドはいつ呼ばれるのでしょうか?
      tableViewメソッド、と言っているのは、どのメソッドを指しているのでしょうか。
      基本的には、loadViewのイベントが実行された時、というのがお答えになるかと思います。

      各tableViewメソッドは基本的に、UITableViewControllerクラスのloadViewメソッドの中でコールされています。

      > そのメソッドをコールしている箇所がなかったりと説明がなかったりで。。。

      定義されているけどコールされている個所がない(ように見える)のは、フレームワークの用意しているフックメソッドだからではないでしょうか。
      そのあたりは、APIリファレンスと対比しながら見て行くと理解が早いと思います。
    • 迷子
    • 2009年03月17日 04:29
    • 詳しい説明ありがとうございます。

      いくつかのサンプルコードとデバッカを使って、
      なんとなく流れが掴めてきました。
      (デバッカの使い方も先日知りました。。。

      リサーチアシスタント機能は便利ですね

      続編、がんばってください
    • おれおん
    • 2011年12月31日 21:52
    • とてもわかりやすい。
      勉強になります。

コメントフォーム

認証
captcha_key
 
 

トラックバックURI

アシアルPHP書籍情報