8545 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

 高速列挙 (Fast Enumeration) は前回説明したコレクションの各要素を1つずつ高速に列挙する (取り出す) 仕組みです。Objective-C 2.0 から搭載された新しい機能です。使い方は簡単です。ただし私はまだ NSArray クラスにしか用いていないので NSDictionary や NSSet に用いた場合にはどうのような挙動をするのかは分かりません。

 まずは前回の book4 の main.m をそのまま土台にして高速列挙の部分だけ書き換えてみます。前回の book4 フォルダをそのままコピーして book5 という名前にしてください。新しく作った book5 の中の main.m を fast.m という名前に変えて下記のように書き換えます。

fast.m




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

実行画面や book プログラムの挙動は前回の第4回の場合とまったく同じになります。


コード説明
6行目
 showBook 関数の引数が (int i, id books) から (id book) に変わっています。

15行目
 book4 の時に main.m にあった int 型変数の i と j はなくなっています。高速列挙では for ループのためにこのような変数を用意する必要はなくなります。

25行目
 case 1:
以前は一旦変数 j に格納していた [ books count ] の値を直接 if 文の条件式に記述しています。そして books に要素があった場合に新しい高速列挙で books 配列の中の要素を1つずつ取り出しています。

29行目
 この部分が高速列挙になります。
for ( Book * book in books ) { ... }
for ( 型名 * 変数名 in コレクション ) と記述するとコレクションの中の要素を1つずつ in の前で宣言している変数に格納していきます。要素番号を表す変数 i や要素数を表す変数 j は必要ありません。したがって次の行では showBook(book) と引数として高速列挙で取り出した book オブジェクトだけを渡せば済むようになりました。これが高速列挙です。

43行目
 case 3: if ( [[ book name ] isEqualToString : ...
高速列挙を使うことによって [[[ books objectAtIndex ; i ] name ] に比べるとメッセージ式の階層が1つ減っています。

54行目
 case 5:
この部分は前回とまったく同じです。ただし今のままでは books に登録される Book オブジェクトはその登録された順番通りに格納されたままです。次回には abc 順やアイウエオ順に並べ替えられるようにしたいと思います。このように abc 順などに並べ替えることをソート (sort) と呼びます。NSArray や NSMutableArray には独自のソートメソッドが用意されています。NSArray は変更できない配列ですのでメソッドの返り値としてソートされた新しい配列を返します。NSMutableArray では変更可能ですのでレシーバーに指定された配列自体をソートします。しかし残念ながら NSArray や NSMutableArray のソートメソッドでは複数のデータ(インスタンス変数)を管理する Book オブジェクトをうまくソートできません。したがって自作でソート関数を作りました。次回第6回ではこの機能も搭載します。関数(メソッドになるかもしれません)の振る舞いとしては books 配列に新しい要素を加える時点で正しい位置に要素を挿入するようにしています。

60行目
 case 7:
[ books count ] の使い方や [[ book name ] isEqualToString : ... の使い方は case 3 と同じです。削除メソッドも removeObjectAtIndex : から removeObject : に変わっています。
 ここでもやはり前回と同じように1度削除が行われた段階で break 文を使って強制的に高速列挙を終了しています。理由は前回の for ループの場合と同じで列挙の数や順番が狂ってしまってエラーがでるからです。for ループの場合は i や j の値をマイナス1とする回避手段もありましたが高速列挙ではその手は効きません。不確かな記憶で誠に申し訳ありませんがこういう場合に残りの Enumerator (列挙子)を取り出すメソッドが NSEnumerator かあるいは Fast Enumerator の中にあったような気がします。もしどうしてもということであれば調べてみて下さい。かなり不正確な記憶で申し訳ありあせんが...

89行目
 showBook 関数の引数は ( int i , id books ) から ( id book ) に変わりました。それに伴い
[[[ books objectAtIndex : i ] name ] .... というメッセージ式は
[[ book name ] ... とネストの階層も減り記述量も減っています。


その他問題点など

1.
 2008年4月28日現在、Google で Fast Enumeration を検索したところ次のような気になる記事がありました。ただし表題を読んだだけで記事の内容まで読んでいないので確かなことは言えないのですが、
 ・高速列挙は NSArray にしか使えない (NSDictionary には使えないということです)
 ・列挙は配列の最後からはじめる方法もある
2番目については今のところ必要性がないので後回しにするとして、気になるのはこの learn ObjC 2.0 revision の元のエディションである learn ObjC 2.0 の更新が止まったままになっています。この revision 版が終れば再開しようと思っていますが、そこではコレクションとして NSMutableDictionary を使うつもりです。Dictionary に対して高速列挙が使えないとちょっと困ったことになります...

2.
 この段階で一応高速列挙の説明も終わりました。あとはソート機能とファイル入出力機能を搭載してこのコーナーを終る予定です。実際に上記のコードにソート機能とファイル入出力機能を搭載したサンプルも完成しています。でもどうしても気になるのが現在のコードの可読性の悪さです。要するに見にくいということです。そこで良くなるかどうかは分かりませんが、main.m から (この第5回では fast.m から) いくつかの機能を別のクラスにわけようかなと考えています。

3.
上記のことに関係することですが、Objective-C のメッセージ送信によるメソッドの実行は、その「呼び出しと実行」の両方の時間を合せると C や C++ のように静的に結びついた関数呼び出しに比べると約2倍の時間がかかります。具体的な数値で言うと Objective-C で約 0.6 秒かかるのと同等の処理を C や C++ は約 0.3 秒で行います。
   荻原剛志「Objective-C MacOS X プログラミング」に掲載のテストプログラムによる
ただしこれは Xcode1 の時の話です。現在ではもっと最適化が進んでいるかもしれません。そして同じメッセージ式が続けて使われる場合にはキャッシュの関係で C および C++ の関数呼び出しとの時間の差はほとんどなくなります。
 どちらにしても上記のファイルを別クラスに分割した場合はオブジェクト間のメッセージ送信の回数は増えて実行速度は体感できるかどうかは分かりませんが遅くなるのは確かです。しかし元々このようなコマンドラインプログラムを Objective-C で書こうということじたいが実行速度や可読性を考えた場合には良い選択とは言えないでしょう。しかしこのコーナーはあくまでも Objective-C という言語やそのベースとなっている C という言語の基礎を説明しようというコーナーです。したがって「C でやれば良い」とか「パフォーマンスを考えるべきだ」というわけでもありません。また説明しようと思っている C や Objective-C についても、例えば「Xcode2入門」柴田文彦著では一切触れられていない言語の説明の部分の一助にでもなれば、というぐらいの目標でこのコーナーを作っています。

 と、長くなりましたが fast.m (つまりは main.m) を分割するかどうか、少しお時間をください。考えてみます。


Controller オブジェクト
 結局、新しいクラスを作ってファイルを分割することにしました。新しいクラスには一応 Controller という名前を付けました。GUI アプリケーションの正式な MVC にはあてはまらないでしょうが、それなりにまとまったかな、と自分では結構納得しています。新しく作った Controller クラスは内部に NSMutableArray のデータも含んでいます。つまり Model の機能も含んでいることになります。また直接コンソールに文字列を表示する機能も付いていますので MVC のすべてに関わっていると言えますが、それなりに C + M の機能をメインとして働いていると思います。なお Book.h と Book.m には一切変更はありません。

main.m


 まずは main.m ですが、fast.m に比べると劇的に見やすくなっていると思います。
6行目では、Controller.h で定義している MAXLENGTH を使っています。main 関数内で宣言している自動変数は char ss、Controller *books、BOOL quit の3つに減っています。
10行目では、プログラムを起動してから終了するまでずっと使い回すことになる Controller のインスタンスを作ってそのポインタを変数 books に格納しています。
 switch 文の case 1 から 7 では Controller クラスに実装されている各メソッドを呼び出して break 文で switch 文を終らせているだけです。case 9 は以前のままだと思います。

Controller.h


3行目
ここで定義された記号定数 MAXLENGTH はこの Controller.h を読み込んだ main.m でも使っています。
7〜9行目
3つのインスタンスのうち、必ずここで(インスタンス変数)として宣言しておかなければならないのは NSMutableArray の books 変数ぐらいだと思います。char 型の ss と BOOL 型の exist は各メソッドの中で宣言したほうが良いかもしれませんが今回は一応ここで宣言しました。
12〜17行目
この Controller クラスに追加する6つのメソッドを定義(宣言)しています。

Controller.m




6行目〜13行目
init メソッド
main.m の10行目、books = [ [ Controller alloc ] init ] ; の init で呼び出されている(メッセージが送られている)のがこの init メソッドです。繰り返しの説明になりますがクラスに対して alloc メソッドを送ることによってインスタンス領域がメモリー上に確保されます。次に init メソッドを送ることによってそのインスタンスのインスタンス変数が初期化されます(設定したい初期値に設定します)。この alloc と init メソッドは NSObject で定義されているメソッドです。これについても前に説明しましたが Objective-C ではすべてのクラスのスーパークラスをたどっていけば最終的には NSObject にたどり着くことになっています。つまりすべてのクラスは alloc と init メソッドを継承していることになります。この Controller.m ではその init メソッドを上書き ( override ) しています。Controller.h では init メソッドは宣言されていませんが、継承するメソッドについてはわざわざヘッダファイルで宣言する必要はありません。また上書き ( override ) する必要がなければ実装も記述する必要はありません。
 self = [ super init ] ; の super はそのオブジェクトのスーパークラスのオブジェクトを表す Objective-C に最初から用意されている名前です。また self はそのオブジェクト自身を表すために Objective-C に最初から用意されている名前(変数)です。init メソッドを上書きする場合にはまず [ super init ] でスーパークラスの init メソッドを呼び出すようにします。こうすることによって継承関係にあるすべての init メソッドが順番に呼び出されていきます。そして最終的には NSObject の init メソッドが呼び出されてとても大切な仕事をします。NSObject の init メソッドでは isa と呼ばれる特殊な変数に自分自身を格納してそのポインターを返します。結果として8行目のように self = [ super init ] ; と記述することによって self が自分自身を表すようになるということみたいです。この場合の自分自身とは NSObject のインスタンスではなく NSObject をスーパークラスあるいはルートクラスとして継承しているサブクラス側のインスタンスになります。
 init メソッドは上書きしていなくても NSObject の init メソッドは必ず呼びだれることになります。そして self が自分自身を表すようになります。しかしこの init メソッドの記述法は確かに一瞬「あれ?」と不思議に感じる部分があります。あまりこだわらずに1つの定型文だと考えて init メソッドはこのように書くのだと覚え込んでしまっても良いかもしれません。
 override で新たに追加したい初期化は11行目のように if (self != nil) のブロック文( { } )の中に記述します。ここでは books に NSMutableArray のインスタンスのポインターを要素数ゼロで格納しています。残りのインスタンス変数 ss と exist については何もしていません。この2つはやはり次回ではそれぞれのメソッド内の自動変数として宣言したいと思います。

15行目〜24行目
isRegistered メソッド
 Objectice-C で最初から用意されているメソッドの中で「何々ですか?」と YES か NO で答えられるメソッドには is何々 というメソッド名が付けられています。それに準じてこのメソッド名を決めました。「登録はありますか?」という意味になります。
 このメソッドはプログラム中で「一覧表示」「指定表示」「削除」が選ばれた場合にそれぞれに対応するメソッド内から最初に呼び出されるようにしています。[ books count ] は fast.m でも説明している通りコレクションの要素数を返します。要素があれば17行目の if の条件式は1以上になり true と評価され retrun で YES を返してこのメソッドを終えます。要素数が0であれば if 文の条件式は0と評価されます。お忘れの場合は learn C 第4回 条件分岐 を読み直してもらいたいのですが、0という値は false として評価されます。結果として「何も登録がありません。」と表示されて NO を返してこのメソッドを終えます。
 ここで1つ考えておきたい事は if else 文は条件式が該当するまで最初の if 文から順番に条件式の評価作業が行われていきます。したがって一番該当するであろう条件式から始めるようにした方が処理過程に無駄が出る可能性は低くなります。
 これに対して switch 文は条件式で評価された整数の値に該当する case 句に直接移動します。従って処理過程に無駄がでることはなくなります。ただし条件式には整数値として評価できるものしか使えません。どちらを使うかはその時々の状況次第だと思います。なおこの isRegistered メソッドでは登録数ゼロという可能性のほうが低いので true の返る条件式を最初にもってきました。

26行目〜33行目
showAllBook メソッド
 if 文の条件式の [ self isRegistered ]で YES になった場合にのみブロック文の中が実行されます。なお self は前述の通り自分自身を表しています。自分自身に isRegistered メソッドを送っていることになります。次の行の for ではじまる高速列挙の式はすでにわかると思います。さらに次の行の [ self showBook : book ] メッセージ式の showBook メソッドは 分割前の showBook(id book) 関数をメソッド化したものです。showBook メソッドは53行目からはじまっていますが内容は showBook(id book)関数とまったく同じです。

35行目〜51行目
showSpecifiedBook メソッド
 最初に isRegistered メソッドで books に登録があるのかどうかを確認しているのは showAllBook と同じです。要素があった場合の振る舞いは fast.m の switch 文の case 3 の場合とまったく同じです。ここでも以前の showBook(id book) 関数ではなく showBook:book メソッドを呼び出しています。

53行目〜61行目
showBook メソッド
以前の showBook(id book) 関数をメソッドに変えただけです。コードの詳細については fast.m の「コード説明」を参考にしてください。

63行目〜97行目
addBook メソッド
Book クラスをインスタンス化して各項目にコンソールから入力された文字列を格納しているところまでは fast.m switch 文 case 5 と同じです。
83行目からは今回追加した独自のソートシステムを記述しています。94行目・95行目では新しく作った book がすでにあるコレクションの昇順内に収まらなかった場合にコレクションの最後に book を追加しています。

99行目〜117行目
removeBook メソッド
最初に isRegistered メソッドでコレクションに要素があるかどうかを確認している以外は前述の fast.m の switch 文 case 7 と同じです。詳細は fast.m の「コード説明」をご覧ください。


addBook メソッドのソート部分
83行目
メソッド冒頭で宣言している int i を0で初期化しています。この変数はメソッド内の自動変数です。メソッドが終れば自動的に破棄解放されます。
84行目
メソッド冒頭で宣言している BOOL insert を NO で初期化しています。この変数もメソッド内の自動変数です。メソッドが終れば自動的に破棄解放されます。
85行目
高速列挙の中で今回は books コレクションから book2 という名前で各要素を順番に取り出しています。追加したい新しいデータにすでに book という名前を付けています。名前が重複しないためにコレクションから取り出すほうは book2 としています。
86行目
if ( [[ book2 name ] caseInsensitiveCompare : [ book name ]] = = NSOrderedDescending )
caseInsensitiveCompare はレシーバーの文字列と引数の文字列を英文字の場合には大文字と小文字の区別は無視して比較します。大文字と小文字も区別して比較したい場合は Compare メソッドを使います。日本語には大文字小文字の区別は最初からありませんが当然このメソッドで比較することができます。
 このメソッドは次の3つの定数値を返します
・レシーバーが引数より小さい場合
  NSOrderedAscending
・レシーバーと引数が同じ場合
  NSOrderedSame
・レシーバーが引数より大きい場合
  NSOrderedDescending

87行目
[ books insertObject : atIndex : ]
insertObject : atIndex : メソッドはレシーバーの NSArray 配列の atIndex の引数の番目に insertObject の引数のオブジェクトを挿入します。
88行目〜93行目
insert = YES ;
新しい book オブジェクトを挿入した場合には insert に YES を代入して break 文で高速列挙を抜けてしまいます。挿入しなかった場合には i を1加算します。
94行目〜96行目
if ( i insert ) インサートが NO ならば addObject メソッドでコレクション books の末尾に引数 book を追加します。



この新しいプロジェクトのコンパイルコマンドは
gcc main.m Book.m Controller.m -o book -framework Foundation -fobjc-gc-only -wall
です。
実行は
./book
です。

 実行すると以前とまったく同じ動作をすると思います。唯一違う点は books にデータを追加する時に自動的に昇順の位置に (アイウエオや ABC の順に) 新しいデータが挿入される点です。なお個々のデータそのものを編集する機能はついていません。もし個々のデータを部分的に変更したい場合でも、そのデータを一度削除して新しいデータを追加する方法しかありません。編集機能を付けなかった理由はサンプルプログラムをこれ以上複雑にしたくなかったからです。
 この新しいサンプルコードは book5.zip からダウンロードすることもできます。





 お疲れさまでした。learn ObjC 2.0 第5回はこれで終ります。次回第6回ではこのプログラムに登録されたデータが終了時に自動的に保存されて起動時にはその保存されたデータが自動的に読む込まれるようにしたいと思います。

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

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