9414 visitors

Objective-C 2.0

no 0  改訂履歴
no 1  オブジェクト
no 2  ガベージコレクト
no 3  プロパティ
no 4  コレクション
no 5  高速列挙
no 6  ファイル入出力


このサイトについて
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

 コレクション (集合) とはデータをまとまて管理する仕組みのことです。Book オブジェクトは一人分の情報を管理しますが、この Book オブジェクトをアドレス帳のように1つの (一冊の) データにまとめるものがコレクション・クラスです。コレクションクラスには
 ・NSArray および NSMutableArray
 ・NSDictionary および NSMutableDictionary
 ・NSSet および NSMutableSet
などがあります。

 NSArray は個々のデータを配列の要素として管理してまとめます。各要素には0からはじまる続き番号が付きます。各要素にはその番号(Index)を指定してアクセスします。ただし C 言語の配列と違うところは各要素にはオブジェクトのポインターならどの種類のオブジェクトのポインターでも格納することができます。
 NSArray では一度決めた要素の数や要素の内容(この場合には要素のオブジェクト)を変更することはできません。それに対して要素の数や要素の内容を変更できるものが NSMutableArray です。

 NSDictionary はキー (key) と (value) をペアとして管理します。ひとつのペアのことをエントリ (entry) と呼びます。エントリのキーには普通は文字列が用いられます。エントリの値には NSArray と同じようにオブジェクトのポインターが格納されます。そして格納されるオブジェクトの種類は問いません。エントリは1つの NSDictionary の中にいくつでも登録できます。要するに NSArray ではそれぞれの要素に番号を付けて管理していたのに対して NSDictionary ではそれぞれの要素に名前を付けて管理しいるという違いがあります。そして NSArray の時と同じように NSDictionary は一度決めたエントリの数と内容を変更することはできません。それに対してエントリの数と内容を変更できるものが NSMutableDictionary です。
 
 NSSet は NSArray や NSDictionary と同じでオブジェクトの集合をまとめます。ただし NSArray などとは違い各要素に順序という概念がありません。したがって Index (番号) やキー (名前) を付けて個々の要素を管理することはできません。NSSetもやはり要素の数や内容を変更できません。それに対して要素の数や内容を変更できるものが NSMutableSet です。

 変更できないコレクションは変更できるコレクションよりも CPU にかかる負荷が少ないそうです。要するに軽く動くということです。そのためにこのように必要に応じて変更できないものと変更できるものを使い分けてプログラムのパフォーマンスを向上させることができるように変更不可クラスと変更可能クラスを用意しているとのことらしいです。

 高速列挙 Fast Enumeration は、このコレクションの各要素に高速にアクセスするために Objective-C 2.0 で新しく搭載された機能です。


ポインターとメモリー管理の必要性
 ここで少しだけ難しい話しをします。前にもメモリーリークとして説明したことと実質的には同じ内容です。大事なことなので繰り返し説明しますが、もし理解できなくても気にせずにどんどん先に進んでください。

Book オブジェクトのインスタンス変数は NSString *型です。NSString は変更ができないクラスです。これに対して NSMutableString という変更可能クラスもあります。つまり Book オブジェクトのインスタンス変数は本来は変更できない型なのです。しかし前回の book3 では Book のそれぞれの値を次々と変更していきました。これはインスタンス変数を NSString 型のポインター NSString *型として宣言していたからです。Book3 ではインスタンス変数の文字列自身を直接書き換えていたのではありません。文字列を変更する場合は、新しい文字列オブジェクトを作ってインスタンス変数がポイントする (指差す) 文字列をその新しい文字列に変えていただけだったのです。したがって変更前の文字列オブジェクトもメモリー上のどこかに残ったままになります。そしてこのように不要になったオブジェクトを破棄してメモリー領域を解放するシステムがガベージコレクションやリファレンスカウンタ方式だったのです。
 稚拙なですがこのあたりがポインターの秘訣 (ポインターを理解するコツ) だと思います。もしこれで理解して頂けるようでしたら幸いです。でももし理解できなくてもそれはあなたのせいではなく説明する私のせいでもあります。そして気にせず先に進みましょう。


NSMutableArray
 前回の book3 を拡張して Book オブジェクトをまとめる NSMutableArray を作って複数の人のアドレスを登録・表示・削除できるようにしたいと思います。

 まず、book3 フォルダをファルダごとコピーして新しく作ったフォルダの名前を book4 にします。book4 の中に入っている Book.h と Book.m はそのまま残しておいてそのまま使います。何の変更もいたしません。book4 フォルダの中の loop.m と makefile は削除してください。この2つはもう使いません。そして main.m を 次のように書き換えます。

main.m




コンパイルコマンドは
gcc main.m Book.m -o book -framework Foundation -fobjc-gc-only -wall
です。
実行は
./book
です。
サンプルファイルは book4.zip からダウンロードすることもできます。

実行画面は下記のようになります。


コード説明
 book3 の main.m の5行目にあったグローバル変数 exist はなくなっています。

 showBook 関数のプロトタイプは
void showBook(id book); から void showBook(int i, id books); に変わっています。
今回は Book オブジェクトを NSMutableArray に登録してまとめていきます。したがってその Array に登録した Book の1つを表示するために「何番目のオブジェクト」かを表す int (整数値) 型の i という値と Book の集合 (NSMutableArray) を表すオブジェクト型の books という値を引数として渡しています。引数の名前が book から books と複数形に変わっています。本当は book から bookArray などの名前に変えれば良いように思いますが、このように集合オブジェクトの変数を表す場合に 名前 s と単に s を付けて複数形にする場合が多いみたいです。

 main 関数の冒頭では今までのように汎用の id 型ではなくそれぞれのクラス名で
Book * および NSMutableArray * と型指定して book 変数と books 変数を宣言しています。また BOOL 型の exist という変数と int 型の i と j という変数も宣言しています。

 冒頭の変数宣言が終ると
books = [NSMutableArray array]; で NSMutableArray のインスタンスを作っています。以前の book3 ではここで book = [[Book alloc] init]; と Book のインスタンスを作っていましたが、今回は「追加」が選ばれてから Book インスタンスを作るように変えまています。NSMutableArray に対して使っている array メソッドは NSArray や NSMutableArray を 要素ゼロとして alloc して init して、おまけに autorelease までしてくれる便利なメソッドです。NSDicitionary にはこれと同様の dictionary というメソッドがあります。最後の autorelease とは作ったインスタンスを NSAutoreleasePool に登録するメソッドです。このように自動的に NSAutoreleasePool に登録されるインスタンスを一時的インスタンスと呼びますがガベージコレクションを選択した今では NSAutoreleasePool を用意しておく必要もなく autorelease メソッドも無視されるだけです。
 終了をするかどうかを表す BOOL 値 quit は当然のことながらまずは NO に設定しておきます。なお BOOL 値は宣言した時点でデフォルトとして NO に設定されています。しかしこのように明示的に NO を設定しておくほうが可読性や間違いを侵さないという意味で良いと思います。

 while 文に入ってからの最初の printf では選択肢が5つに増えています。なお while 文については「learn C 第5回 ループ文」の while 文 を参考にしてください。

switch 文 case 1
 switch 文の case 1: ではまず j = [books count]; で books 配列に count メソッドを送って books にいくつの要素が入っているかを調べています。そしてその数を変数 j に代入しています。次に if 文では j が 0 なら「何も登録がありません。」と表示されて switch 文は一旦終了して while 文の先頭に戻ります。
 j が 0 でなければ for 文で books 配列の各要素を番号0から最後の要素まで showBook 関数を呼び出してターミナルに表示していきます。なお switch 文については「learn C 第5回 条件分岐」の switch 文 を参考にしてください。for 文については「learn C 第5回 ループ文」の for 文 を参考にしてください。

switch 文 case 3
 switch 分の case 3: ではまず case 1 の場合と同じく books 配列の要素数を調べます。要素数が 0 の場合は case 1 の時と同じ動作をします。要素数が 0 でなければ 1 件以上の登録があることになります。まず表示したい人の名前を入力してもらいます。BOOL 型の exist (存在するという意味) 変数は NO で初期化しておきます。そして for 文で要素番号 (これを添え字もしくは index と呼びます) 0 から最後の要素まで同じ名前が登録されているかを1つ1つチェックしていきます。if 文の条件式には次のメッセージ式が書かれています。

[[[ books objectAtIndex : i ] name ] isEqualToString : [ NSString stringWithUTF8String : ss ]]
 これは次の 4 つのメッセージ式がネスト (入れ子状態に) されています。

1. [ books objectAtIndex : i ]
books 配列の i 番目のオブジェクトつまり i 番目の book オブジェクトを取り出しています。

s 2. [ i番目のbookオブジェクト name ]
i 番目の book オブジェクトの name プロパティ、つまり文字列オブジェクトを取り出しています。

3. [ 文字列オブジェクト isEqualToString : 文字列オブジェクト ]
isEqualToString は NSString インスタンスに対して使えるメソッドです。レシーバーの文字列オブジェクトと引数の文字列オブジェクトが同じかどうかを BOOL 値の YES か NO かで返します。
 つまりこのネストされた if 文の条件式は books 配列の i 番目の book の name インスタンス変数の文字列と、コンソールで入力された文字列が同じかどうかを調べています。
 ただしターミナルで入力された文字列は C 言語の文字列です。この C 言語の文字列を

4. [ NSString stringWithUTF8String : ss ]
と NSStiring のクラスメソッドである stringWithUTF8String : で UTF-8エンコーディングを指定して NSString の文字列オブジェクトに変換してます。引数 ss はターミナルで入力された C 文字列が格納されている変数です。

 なおエンコーディングとは文字をコード番号で管理するシステムのことです。各文字はコード番号によって識別されています。しかしこのエンコードは何種類も存在していてエンコードの種類が違えば各文字に割り当てられているコード番号も違います。ウェブやメールで文字化けが起こるのはこのエンコードの指定が違っているからです。このサイトではすべてのエンコードを UTF-8 にしています。サンプルプログラムも UTF-8 にしています。現在、エンコードをこの UTF-8 にしていこうという動きが主流になっています。

 名前が一致すれば showBook 関数を呼び出してその book データを表示します。そして exist 変数も YES にしておきます。この case 3 の for 文では books 配列の中に一致した名前が複数あればそのすべてを表示します。<最後に if ( ! exist ) で exist が NO のままの場合には
「お名前が見つかりませんでした。」と表示されるようにしています。

switch 文 case 5
 case 5 は、books 配列に Book オブジェクトを追加(登録)するところです。まず
 book = [[ Book alloc ] init ] ;
で新しい Book インスタンスを作っています。この Book クラスは自作のクラスですので先ほどの NSArray の array メソッドのように1つのメッセージで alloc init autorelese を行ってくれるような便利なメソッドは搭載していません。あるクラスのインスタンスを作る場合、普通はこのように
 変数 = [[ クラス名 alloc ] init ] ;
とするのが一般的です。ガベージコレクションを採用しているの場合は続けて autorelease メッセージを送る必要はありません。この alloc というクラスメソッドと init というインスタンスメソッドはスーパークラスの NSObject から継承したものです。この2つのメソッドは Book クラスの定義部分や実装部分で一切記述していませんが MSObject を継承することによってこのように当たり前のように使うことができます。

 次に Book インスタンスが格納されている変数 book を引数にして addBook 関数を呼び出しています。addBook 関数では 引数として受け取った Book インスタンスの各インスタンス変数にそれぞれの setter を使って値を格納しています。

 [ book setName : [ NSString stringWithUTF8String : ss ]]

 setName の引数となっている [ NSString stringWithUTF8String : ss ] は、さきほども説明しましたが再度説明いたします。
 NSStiring のクラスメソッドである stringWithUTF8String : は UTF-8エンコーディングで引数 (ここでは ss) の C 文字列を NSString の文字列オブジェクトに変換します。引数 ss はターミナルで入力された C 文字列が格納されている変数です。

 addBook 関数が完了するとプログラムの制御は switch 文 case 5 に戻ってきて
[ books addObject : book ] ;
が実行されます。addObject : は引数のオブジェクトを NSMutableArray 配列の最後に要素として加えるメソッドです。

switch 文 case 7
 case 7 では、case 3 と同じ手法でコンソールで入力された名前と同じ名前を持つ book オブジェクトを配列 books から探しています。そして同じ名前が見つかった場合は
 [ books removeObjectAtIndex ; i ] ;
で該当した book オブジェクトを books 配列から削除しています。そして「削除しました。」とコンソールに表示して exist 変数に YES を代入します。
 case 7 が case 3 と大きく違うところはここで break 文を使って強制的に for ループを中断している点です。removeObjectAtIndex: で配列から要素を削除した場合は削除した位置よりあとの要素は1つずつ前につまります。そしてこのまま for ループを続けると次の2つのエラーが起こります。

1. 要素が1つずつ前倒しになるので削除した要素の次の要素はすでにチェックされた要素番号と同じ番号になる。結果としてその要素はチェックされないままになる。

2. for ループの最後の値(総要素数) j は 最初に [ books count ] で導き出された値のままです。しかし実際の配列の要素数は1つ減っているのでこまま for ループを続けると最終的には配列の範囲を飛び出してしまいエラーとなりプログラムが強制終了してしまう。

 この2つの問題を回避するにはオブジェクトを削除した時には変数 i と j の値をマイナス1とするなどの手があります。しかし「削除」することじたい慎重に行われたほうが良いと思いますので仮に同じ名前が複数登録されていても1度に1つづつしか削除されないままで良いような気がしましたので break 文で中断する方法を選びました。またすでにサンプルのソースコードが十分に長いのでこれ以上長くしたくないという理由もあります。

 case 7 の最後では ( ! exist ) で「exist が NO」なら
「お名前が見つかりませんでした。」と表示されるようになっています。

switch 文 case 9
 quit 変数に YES を代入しているだけです。次の break; で switch 文が終ると while 文の最初に戻ります。 すると今度は while 文の条件式に合わなくり while 文 は実行されることなく main 関数の最後の return 文まで進みプログラムは終了します。





 お疲れさまでした。learn ObjC 2.0 の第4回はこれで一応終ります。
次回は予定を変更してファイル入出力より先に高速列挙の説明をしたいと思います。
サンプルプログラムがすでにかなり長い行数になっています。高速列挙で少しでも読みやすいコードに変えたいと思います。そして加えて main.m の中からいくつかの部分を別のファイルに、つまり新しいクラスとして分割するかもしれません。

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

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