25898 visitors

Objective-C 2.0 簡易版

no 0  改訂履歴
no 1  オブジェクト
no 2  ガベージコレクト
no 3  プロパティ
no 4  コレクション


このサイトについて
contact me
home

Programming
C 言語
learn C
Objective-C 2.0 言語
learn ObjC
Objective-C 2.0 言語 簡易版
learn ObjC  Lite
Cocoa GUI アプリケーション
Cocoa GUI App
メモリ管理検証  new
Memory Management Test

Other
参考図書・グッズ
Favorites
ソフトウエア
Software  soon

Legacy
古い記事
Objective-C Primer
 まず第1回で作成したプログラムをフォルダごとコピーします。ファインダー上で lite フォルダを開き lite1 フォルダを option + ドラッグでコピーして lite2 と名付けるか、もしくはターミナルを起動して cd lite で lite ディレクトリに移動してから
cp -r lite1 lite2
で lite1 を lite2 という名前でコピーします。cp コマンドの -r オプションは「ディレクトリ(フォルダ)を再帰的にコピーする」という意味です。簡単に言うとフォルダの中身もコピーするということになります。そして lite2 の中の intro.m を Apple社の指針に基づき3つのファイルに分割していきます。なおディレクトリとフォルダは同じ意味です。UNIX のコンソールやターミナル上ではディレクトリと呼び、Mac などの GUI OS ではフォルダと呼ぶことが多いみたいです。

次に Xcode を起動して「ファイル」「新規ファイル...」「プロジェクト内の空ファイル」と進んでいき ITModel.h というファイル名で新しく作った lite2 フォルダに保存します。次に lite2 フォルダの中の intro.m を開き、1行目から13行目までをコピーして新しく作った ITModel.h にペーストします。
#import <stdio.h> と #import <stdlib.h> そして #define MAXLENGTH 200 の3行は削除します。

ITModel.h


 次に先ほどと同じ方法で ITModel.m というファイルを lite2 フォルダに保存します。そして intro.m の15行目から30行目までをコピーして ITModel.m にペーストします。そして ITModel.m の1行目の @interface の前に
#import "ITModel.h"
と書き加えます。ITModel.m はコンパイルするために Foundation/Foundation.h と ITModel.h の両方の情報を必要としています。Foundation/Foundation.h は ITModel.h ですでに読み込んでいますので ITModel.h を読み込むことによって両方を読み込むことができます。

ITModel.m


アップルではこのように1つ1つのクラスごとにヘッダファイル(クラス名.h) と実装ファイル(クラス名.m) に分けて保存するように指導しています。

最後に intro.m からクラスの定義と実装の部分を削除しファイル名も intro.m から main.m に変更します。ファイル名の変更はファインダー上でファイルの名前部分をクリックなどして変更してもらっても結構ですし、ターミナル上の次のコマンドでも変更できます。
まず
cd lite/lite2
などで lite2 ディレクトリに移動します。次に
mv intro.m main.m
で intro.m を main.m という名前に変更できます。なお名称を変更する時には intro.m を閉じておいたほうが良いでしょう。

 C および Objective-C ではプログラムは main 関数から始まります。しかし Objective-C において main 関数のあるファイルを必ずしも main.m とする必要はありません。多くの場合 main 関数を含むファイルを プログラム名.m とする場合が多いみたいです。

main.m


 1行目を #import <Foundation/Foundation.h> から #import "ITModel.h" に変更します。main.m はコンパイルするために Foundation/Foundation.h と ITModel.h の両方の情報を必要としています。
Foundation/Foundation.h を読み込んでいる ITModel.h を読み込むことによってその両方の情報を読み込むことができます。
 では早速コンパイルして実行してみましょう。まず pwd コマンドなどでターミナル上の lite2 ディレクトリに居ることを確認してください。コンパイルコマンドは
gcc main.m ITModel.m -o intro -framework Foundation -wall
です。実行は
./intro
です。第1回のプログラムとまったく同じ動作をすればファイルの分割は成功しています。
このサンプルプログラムは lite2.zip からダウンロードすることもできます。


main.m

 前回の第1回でクラスの部分 (ITModel.h と ITModel.m) についての説明は終っています。この回では main.m のソースについて説明していきます。そして今回の目玉は Objective-C 2.0 から搭載された新機能のガベージコレクションになるでしょう。

1行目
ここでも ITModel.h を読む込むことによって Foundation/Foundation.h と ITModel.h の両方の情報を読み込んでいます。

2行目〜8行目
前回の intro.m とまったく同じなので説明は省略します。

9行目
id はオブジェクトのポインターならなんでも格納することのできる型です。

10行目
BOOL は YES か NO かだけを格納できる型です。実際には 0 以外の整数(マイナス値も含む)はすべて YES として評価され 0 だけが NO と評価されます。YES は 1、NO は 0 の記号定数です。

13行目 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSAutoreleasePool (自動解放プール) と呼ばれるクラスのインスタンスを作っています。自動解放プールは旧Objecitve-C で採用されていたメモリー管理システムで使われていたものです。このサンプルプログラムを例に挙げると「編集=2」を選んで入力した文字列は ITModel オブジェクトに格納されますが、前に格納されていた文字列そのものが変更されたわけではありません。古い文字列の代わりに新しい文字列を格納しているだけで前の古い文字列はそのままメモリー上のどこかに残ったままになっています。このこととをメモリーリーク(メモリー漏れ) と呼びます。そしてこのようなことを繰り返し行っていくとやがてプログラムやシステム (OS) が必要とするメモリーが足りなくなってしまいます。そこで旧Objective-C ではオブジェクト内にリファレンスカウンタ(参照カウンタ)と呼ばれるインスタンス変数を持っていてオブジェクト(インスタンス)が作られた場合にその値が1になります。オブジェクトは他の複数のオブジェクトからも必要とされる場合もあります。その場合には [必要なオブジェクト retain] というメッセージを送ればそのオブジェクトのリファレンスカウンタの値は1プラスされます。またあるオブジェクトが不必要となった場合には [不必要なオブジェクト release] とメッセージを送ればそのオブジェクトのリファレンスカウンタは1マイナスされます。こうして最終的にリファレンスカウンタの値が0となったオブジェクトはそのオブジェクト自身が自らを破棄して支配していたメモリーを解放します。

 自動解放プールはこの release メッセージを省略するためのもので [[[クラス alloc] init] qutorelease] と記述するとそのオブジェクトは自動解放プールに登録されます。そして main.m の37行目のように [自動解放プール drain] もしくは [自動解放プール release] とすると自動解放プールに登録されているすべてのオブジェクトに対して release メッセージが送られます。多くのオブジェクトは複数から参照されることのほうが少ないです。したがってこれでそのオブジェクトは破棄されメモリーは解放されることになります。

 また main.m 29行目の [model setString:[NSString stringWithUTF8String:ss]]; では ITModelクラスのインスタンスが代入されている model 変数の setString メソッドを呼び出しています。そしてその引数として [NSString stringWithUTF8String:ss] という NSString クラスの stringWithUTF8String: クラスメソッドを呼び出しています。クラスメソッドはクラスに対して直接使える (クラスをインスタンス化せずに使える) という点で結構便利なメソッドです。この stringWithUTF8String: クラスメソッドは引数 (今回の場合は ss) の C 文字列から NSString 文字列を作成しますが、同時に自動解放プールへの登録も自動的に行います。このように自動解放プールへの登録を自動的に行うオブジェクトを一時的オブジェクトと呼びます。このようなメソッドはかなりの数が存在します。

 ただしこのサンプルプログラムでは main 関数の始まり、つまりプログラムの始まりで自動解放プールが作成されて、main.m の37行目のプログラムの終了時になってやっと drain (release) メッセージが送られているのでメモリー効率としてはほとんど意味はありません。しかし効果があろうがなかろうがこれを記述しておかないとプログラムが実行時にエラーを出すのも事実です。また自動解放プールはネスト (入れ子) にすることができます。つまり自動解放プールの中に自動解放プールを作成することができます。そして一時的オブジェクトや autorelease メッセージが送られたオブジェクトは一番直近に作成された自動解放プールに登録されます。
 なお main.m 37行目で使っている drain メソッドは Mac OS X 10.4 以降から使われるようになったメソッドですが、その詳細はリファレンスを読んでも今ひとつ分かりませんでした。取りあえず [自動解放プール release] の代わりになるものであることは間違いないみたいです。当然 [自動解放プール release] と記述しても問題はありません。

 メモリー解放の話が続きますが、learn C では関数内で宣言された変数は static などの指定子をつけない限りその関数が終了する時に自動的に解放される自動変数であると説明しました。このことは Objective-C の関数内やメソッド内で宣言された変数にもあてはまります。ただし思い出して頂きたいのは「オブジェクトは常にポインターとしてやり取りされる」ということです。関数内やメソッド内で宣言された変数に格納されているのはオブジェクトのポインター、つまりアドレスです。その変数を破棄してもオブジェクトの実体は残ったままです。おまけにその実体を探し出すためのアドレスは失われることになります。

 なおプログラムで使用されているメモリー領域はそのプログラムが終了するとすべて解放されるということは覚えておいてください。

14行目
model = [[ ITModel alloc ] init ] ;
クラスからインスタンスを作る場合の最も一般的なコードです。まず [ クラス alloc ] でクラスをインスタンス化してインスタンス変数の領域をメモリー上に確保します。この最初のメッセージ式で返される値はクラスのインスタンスです。次に [ インスタンス init ] でインスタンスを初期化しています。init メソッドが返す値は自分自身のポインターでしたね。そしてそのポインターを model 変数に代入しています。このコードで作られたインスタンス(オブジェクト)は自動解放プールに自動的に登録される一時オブジェクトではありません。自動解放プールに登録しておきたい場合は
[[[ クラス alloc ] init ] autorelease ] と一行にまとめて記述するのが慣例となっています。

15行目については説明は必要ありませんよね。もし分からないようであれば learn C 第2回 関 数 を読んでみてください。

17行目以降については後ほどイベントループとしてまとめて説明いたしますが、24行目のみは先に説明しておきます。
printf の変換指定子 %s の変換元として
[ [model string ] cStringUsingEncoding : NSUTF8StringEncoding ] となっています。これはまず、
[ model string ] で ITModel オブジェクトの string インスタンス変数に格納されている NSString 文字列を取得しています。そして次に、
[ NSStringインスタンス cStringUsingEncoding : NSUTF8StringEncoding ] というメッセージ式で NSString 文字列をターミナルで表示できる C 文字列へ変換しています。その時に引数で指定されている文字エンコーディングが使われます。cStringUsingEncoding というメソッド名は長いですが意味は分かりやすいと思います。引数の NSUTF8StringEncoding は UTF-8 を表していますが単なる記号定数と同じで内容は整数の 4 です。代わりにこの部分を cStringUsingEncoding : 4 と書き換えてもまったく正常に動作します。しかし 4 のままではすぐに意味は忘れてしまいますので NSUTF8StringEncoding と記述することになっています。

ガベージコレクション

ここまでの説明でもお分かり頂けたと思いますが、旧来のメモリー解放方式は大変面倒なものでした。しかし Objective-C 2.0 では新たにガベージコレクションというメモリー解放方式が採用されました。このガベージコレクション方式は Java をはじめ多くのプログラミング言語ですでに採用されていて Objective-C でも早急に採用されることが望まれていたものです。ガベージコレクションを直訳するとゴミ集めという意味になります。

 ガベージコレクションではメモリー解放についての記述は一切必要ありません。すべてプログラム側で自動的にメモリーを解放してくれます。しかし旧来の方式と比べて不利な点もあります。
・メモリー解放の時期が指定できない
・プログラムの実行速度が少し遅くなる
などがあります。しかしそれらを差し引いてもメモリー解放という面倒な作業から解放される利点は大きいものがあります。そして Objective-C 2.0 では旧来の方式 (リファレンスカウンタ方式と呼びます) とガベージコレクションのどちらも使用できます。どちらか一方だけを使うことも、あるいは両方を混在させることもできます。

 では早速試してみましょう。まず lite2 フォルダの中の main.m のバックアップを一応取っておきます。ターミナル上でlite2 ディレクトリにいることを確認して
cp main.m main2.m
とします。当然この作業はファインダー上で option + ドラッグで行っても構いません。
新しく作った main2.m はバックアップとしてそのまま残しておき、main.m を Xcode で開きます。そして自動解放プールに関する12行目・13行目・37行目を削除します。

main.m


この状態で lite2 ディレクトリ内で次のコマンドで再度コンパイルしなおします。
gcc main.m ITModel.m -o intro -framework Foundation -wall
実行は
./intro
です。



上記のようにエラーメッセージが多数表示されると思います。エラーの内容は
「一時オブジェクトを登録する自動解放プールが存在しません。メモリーリークしています」という意味になります。

次にコンパイルコマンドを以下のように変更してみます。
gcc main.m ITModel.m -o intro -framework Foundation -fobjc-gc -wall
実行はいつもの通り
./intro
です。



 今度は正常に動作したことと思います。
ガベージコレクションを有効にするにはコンパイル時に -fobjc-gc オプション指定します。このオプションには -fobjc-gc-only というものもあります。この2つの違いは

-fobjc-gc
以前からのメモリー解放方式(リファレンスカウント方式)とガベージコレクションの両方を使う場合

-fobjc-gc-only
ガベージコレクション方式のみでメモリー解放を行う場合

となります。今回のサンプルプログラムではどちらでも大丈夫です。また NSAutoreleasePool に関する2行(コメント行も含むと3行)は、ガベージコレクションを選んだ場合には削除してもらっても残しておいてもらってもどちらでも結構です。今回の場合はガベージコレクションを選んだ時点で NSAutoreleasePool はなんの意味もなさなくなっています。この「learn ObjC  Lite」では今後ガベージコレクション方式を採用していきます。なおガベージコレクションは Mac OS X 10.5 Leopard 以降でのみ有効です。ご了承のほどよろしくお願いいたします。

 なおガベージコレクションをプログラムファイルのソースコードの中から有効にする方法が ADC のリファレンスに載っていましたが私の技量不足のせいかうまくいきませんでした。また本来ガベージコレクションはコンパイル方式のオプションなので、この「learn ObjC  Lite」ではコンパイルコマンドでガベージコレクションを指定する方式を採用しています。GUI アプリケーションの場合には Xcode の「ターゲットの情報」パネルから指定します。




イベントループ

 ユーザー (プログラムを使う人) が終了を選ばない限り起動し続けるプログラムを作るにはイベントループという仕組みが必要となります。イベントループはメインループやアプリケーションループとも呼ばれます。実際に GUI を持った Cocoa プログラムの場合にはプログラムの冒頭の main 関数 の中ですぐに NSApplicationMain という関数を呼び出してイベントループを開始します。通常はこのように main 関数の中で イベントループ関数を呼び出す形にしますが、今回のサンプルプログラムでは便宜的に main 関数の中に直接イベントループを記述しました。それが17行目から35行目に当たります。

17行目
quit という BOOL 型の変数に NO という値を代入しています。BOOL 型は初期化せずに宣言した場合には NO に設定されています。しかしここで敢えて quit (終了) は NO であると明示しているわけです。こうすることによってプログラムの可読性 (意味の分かりやすさ) が良くなります。

18行目
while 文の条件式を ! (Not 演算子) を使うことによって「 quit (終了) でなければ」という意味にしています。このようにイベントループでの while 文では YES か NO か (true か false か) を表す変数を用いることが常套手段となって quit が YES (true) になるまではループを繰り返す仕組みを作っています。そしてループの中にはサンプルプログラムのように switch 文を用いてユーザーからの命令を振り分ける仕組みにするのが一般的です。今回の例では至って単純なイベントループですが実際のプログラムでは switch 文の中にさらに switch 文をネストするなどして簡単なプログラムでもこのイベントループの部分だけでも5ページは記述することになります。しかしほとんどの場合はコピー & ペーストの効く部分でもありますので Cocoa アプリケーションプログラムのように NSApplicationMain という関数が用意されているのは至って妥当な方法だと思います。NSApplicationMain がどのような実装になっているかを知るすべはありません。逆に言えば Cocoa GUI アプリケーションの場合には今回のようなイベントループを一切記述する必要はありません。なお switch 文や while 文が分からない場合には learn C 第4回 条件分岐 learn C 第5回 ループ文 ご覧になってください。

19行目
理解されているものと考えて説明を省略させて頂きます

20行目
fgets ( ss, MAXLENGTH, stdin ) ;
fgets は純然たる C 言語の関数で stdin に入力された文字列を 記号定数 MAXLENGTH で指定された数の文字数までを 文字列変数 ss に格納します。stdin とは特に指定のない限りターミナルからの入力を表しています。記号定数 MAXLENGTH はこの main.m の4行目で200に設定されています。この fgets 関数を使用するのに必要な stdio.h ヘッダファイルはこの main.m の2行目で読み込んでいます。

22行目
switch 文の条件分岐に使われている atoi 関数も C 言語の関数です。引数 ( ) 内の文字列を整数値に変換します。数字はそのまま整数に、0001 や 0002 はそれぞれ 1 や 2 に、3abcd2 などは 3 に、文字ではじまる文字列や文字だけの文字列は 0 に変換されます。この関数を使用するのに必要な stdlib.h ヘッダファイルはこの main.m の3行目で読み込んでいます。

switch 文の case 1: と case 2: は今までの説明ですでに理解して頂いているものと思います

30行目
case 3: では quit 変数に YES を代入しています。そして16行目に戻り while 文の判定の時に「 quit (終了) でなければ」という条件に合わなくなり while 文を飛ばして34行目と35行目が実行されてこのプログラムは終了します。





お疲れさまでした。これで「learn ObjC  Lite」の第2回目は終ります。


目次を表示 (先頭へ戻る) 前ページ   次ページ

This site is available in Safari and Leopard. © ttezu 2006 - 2008