17808 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
learn ObjC 2.0 は全面改訂いたしました。たぶんこちらの改訂版のほうが少し難しいと思います。
改訂前の learn ObjC 2.0 も残しておきます。よければ合せてご覧ください。


はじめに
MVC
 オブジェクト指向プログラミングには MVC (モデル・ビュー・コントローラー) という考え方(デザインパターン)があります。オブジェクト指向プログラミング言語の種類によて MVC のそれぞれの役割には微妙な違いがあるいたいですが、Objective-C ではモデルは例えばアドレスブックの各個人のデータおよびその集合を保持する部分を表し、ビューはユーザーの目に触れ操作の対象となる部分(例えばウィンドウやボタンなど)を表し、コントローラーはそのモデルとビューを同期させる役割を果たします。
 なぜ MVC という考え方が必要になるかというとプログラムが巨大化・複雑化してくるとこのような取り決めに沿ってプログラミングしていかなければだんだんと収拾がつかなくなってくるからです。しかし小さなプログラムでもこのような取り決めに沿ってプログラミングをしていくことは結構有効なことだと思います。実際にアップル(ADC)のガイドラインでは簡単なチュートリアルの中で単にメッセージ(指令)をビューから受け取ってそのままモデルに同じメッセージを送っているだけのコントローラーを作っています。そしてこのようなコントローラーは CPU に余分なオーバーヘッド(負荷)をかけることになることも認めた上で、それでも作っておくべきだとは書いてありました。ただしサンプルになっていたのは GUI プログラムでした。

 ところが前回の「learn ObjC 2.0」を掲載しているときに知人から「 MVC は GUI プログラムでのみ有効な考え方ではないか」との話が出てきました。ようするにコマンドラインプログラムでは MVC デザインパターンは無意味だということです。しかし「必ずしもそうとも言い切れない」との意見もありました。そこで少し悩んだのですが、どちらにしても前回のサンプルプログラムではコントローラーの部分が単にビューからの指令をそのままスルーしてモデルに伝えていただけでした。そのうちもう少し何かの役割を果たすようにならないかなぁと期待していたのですが、そういう雰囲気も一向に見えてきていませんでした。また無意味なコントローラクラスがあることでプログラムの全体像が見えにくくもなっていました。そこで改訂することを考えていました。

 また「learn ObjC 2.0」の前シリーズ「learn C」の最後で構造体の説明をして、この構造体に関数を付け足したものをオブジェクトとして捉えて考えていくのは便利な考え方だと説明していたことも思い出しました。そこで今回は MVC のことは一旦置いといて「learn C」に続くかたちですべてを全面改訂することにいたしました。ただしモデル・ビュー・コントローラーという言葉は今後も出て来ると思います。言葉として記憶にとどめておいて頂けると助かります。

 今回も説明は非常に線的になると思います。つまり1からはじまって順序通りに読まないと分からないような形でしか説明することができないだろうと思います。しかし今後できるだけ参照箇所へのサイト内リンクなどを張るようにしていきたいと思います。
 ではまず Xcode とターミナルのデフォルトエンコーディングを UTF-8 にしてください。確認方法や設定方法が分からない場合は こちら を参照してください。また Xcode のインストール方法やターミナルのその他の設定方法などは learn C 第一回 を参照してください。


オブジェクト

  前シリーズ「learn C」の最終回(第6回)で作った構造体の struct.c をオブジェクトで作り替えた object.m を作ってみました。
object.m



サンプルコード object.m は object.zip からダウンロードできます。
ダウンロードした object フォルダをホームフォルダにでも置いていただいてターミナルを起動します。もちろんすべてを手書きしてもらうことにこしたことはありません。
cd object
などで object.m のあるフォルダに移動します。
コンパイルコマンドは
gcc object.m -o object -framework Foundation -wall
です。
実行は
./object
です。
Objective-C のソースファイルの拡張子は .m になります。中に書かれたコードがすべて C 言語のままでも .m 拡張子をつけると Objective-C ファイルとしてコンパイルされます。コンパイルコマンドには上記にように
-framework Foundation というオプションを必ずつけなければなりません。また
-wall
はソースコードの構文チェックを一番厳しくしてすべてのエラーや警告を表示するように指示するオプションです

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



struct.c の時とあまり変わりませんが、引数をコピーとして受け取った場合とポインターとして受け取った場合の使用バイト数の比較表示はしていません。

Objective-C では オブジェクトのやりとりはすべてポインターでおこなう ことになっています。使用バイト数の比較をしても意味はなくなりました。またすべてポインターで行いますので、今までポインターが理解しにくかった人でも、今後ポインターを意識する必要はあまりなくなると思います。普通の変数の感覚で記述していけるようになると思います。

 使用サイズの比較をする必要がなくなったので struct.c にはあった dispCopy 関数と dispPointer 関数は取り除いて、表示の指示は main 関数の中でするようにしています。


クラスとインスタンス
 オブジェクトを使うにはまずその新しいオブジェクトを定義しなければなりません。構造体の時にまず構造体の宣言をしたのと同じことです。ともにプログラマが作る新しい型となるので定義しておかなけばならない理屈は分かります。この object.m では Book という新しいオブジェクトの定義を5行目から55行目で行っています。はじめて見る方には驚くほどの長さだと思います。しかし段々と慣れてくると思います。このようにオブジェクトを定義しているものをクラスと呼びます。

 次にこのクラスを設計図にして実際にメモリー上にオブジェクトを確保します。このことをオブジェクトのインスタンス化(実体化)と呼びます。object.m では63行目の book = [[Book alloc] init];  でインスタンス化を行っています。オブジェクトのインスタンス化はたった1行ですみます。そしてこのインスタンス化されたオブジェクトのことをインスタンスと呼びます。

 クラス名は5行目のように @interface Book : NSObject と @interface の次に宣言します。ここでは Book というクラス名にしています。「learn C」の構造体では addressBook という名前にしていましたが Objective-C には NSAddressBook というクラスがすでに存在しています。そこで今回は Book というクラス名にすることにしました。クラス名はアルファベットの大文字で始める事になっています。そして自作クラスには prefix (接頭辞) を付けることが推奨されています。例えば今回の Book クラスの場合には VCBook や TTBook などと命名するのが正しい形です。prefix (接頭辞) はプログラマの属する会社やグループを表すアルファベットの大文字2〜3文字が使われます。
 Objective-C に標準で用意されているクラスにはほとんど NS という prefix が付いています。これは Objective-C 言語の前の所有者であるスティーブ・ジョブス率いる Next Step 社の略です。Objective-C 言語の現在の所有者は Apple 社です。クラス名の後には @interface Book : NSObject のようにコロン : に続いてスーパークラスが指定されます。スーパークラスについては後ほど説明します。

 Objective-C では、というより Apple ではこのクラス定義部分を別ファイルとして保存するように指導しています。具体的には @interface (ここでは5行目) から次の @end (ここでは20行目) までをインターフェース部として クラス名.h という名前と拡張子で保存します。そして @implementation (ここでは22行目) から次の @end (55行目) までを実装ファイルとして クラス名.m という名前と拡張子で保存します。次回ではまず最初にクラスの定義ファイルの分割したいと思います。


ヘッダファイルの読み込み
object.m 1行目
#import <Foundation/Foundation.h>
Objective-C ではヘッダファイルの読み込みには #include ではなく #import を使います。#include では指定されたヘッダファイルを #include を記述するたびに読み込んでしまいます。例えば次回には上記の object.m ファイルをいくつかに分割しますが、その時にうっかり間違って同じヘッダファイルを2重に読み込むように記述してしまうかもしれません。すると標準で用意されている関数やクラスなどの定義が2重になることになりエラーになる可能性があります。それに対して #import で読み込むヘッダファイルは2重に読み込みが起きない事が保証されています。従って Objective-C では #import を使うようになっています。2行目では C の stdio.h ヘッダファイルも #import で読み込んでいます。

  Cocoa には約300のクラスが最初から用意されています。そしてその約半数は Foundation と呼ばれる今回使っている NSString のようなプログラムの基礎的な部分を受け持つクラスです。<Foundation/Foundation.h> というヘッダファイルはこの Foundation と呼ばれるクラス群のヘッダファイルをすべてまとめて読み込むものです。これに対してプログラムの GUI の部分を受け持つクラス群のことを AppKit もしくは ApplecationKit と呼びます。そして AppKit.h を読み込むことによって AppKit クラス群のヘッダファイルをすべてまとめて読み込むことができます。さらに <Cocoa/Cocoa.h> とすると Foundation クラス群と AppKit クラス群のヘッダファイルをすべてまとめて読み込むことができます。なおヘッダファイルを読み込む時に開発環境によって最初から用意されているヘッダファイルは < >で囲みます。自作したクラスのヘッダファイルはそのヘッダファイルまでのパスを " " で囲みます。通常は必要なファイルを同じフォルダに保存しますので "ヘッダファイル名.h" で読み込むことになります。

 なお個別のクラスのヘッダファイルを見るにはクラス名の上にカーソルを持って行きコマンドキー(⌘、command)を押しながらダブルクリックします。例えば object.m の7行目の NSString という文字の上で試してみてください。Xcode 3 ではこの操作で NSString のヘッダファイルが開くことになっています。しかしこれで開かない場合もあります。私の場合も開きません。その場合は起動ボリュームの第1階層から システム / ライブラリ / Frameworks / Foundation.framework / Headers と開いていき、その中にある NSString.h をダブルクリックすることで開けます。
 ずっと下にスクロールしていくと先頭に + や - のついた文字列が現れてきますが、これがメソッドと呼ばれるものです。言わばそのオブジェクト専用の(そのオブジェクトだけで使える)関数みたいなものです。なお NSString の実装ファイル NSString.m はもうすでにこのように目で見ることができません。このように最初から用意されているクラスはすでにコンパイルされて機械語の状態で待機しています。


継承 inheritance
 すべてのオブジェクト指向言語は継承という仕組みを持っています。あるクラスを継承して新しいクラスを作ることができ、その新しいクラスは元のクラスのすべてのインスタンス変数とすべてメソッドを持つことができ使うことができます (一部アクセスできないインスタンス変数がある場合があります。またプログラムの中で直接呼び出すコードを書いてはいけないメソッドもあります)。加えて新たなインスタンス変数やメソッドを追加することができます。また継承したメソッドの実装を変更することもできます(このことを上書き overrideと呼びます)。新しいオブジェクトを作る場合には普通は一から作らずに作りたいオブジェクトの機能のおおかたをすでに実装しているクラスを探してそのクラスを継承して新しいクラスを作ります。この元となるクラスのことを親クラスもしくはスーパークラスと呼びます。そしてスーパークラスを継承して新しく作ったクラスのことを子クラスもしくはサブクラスと呼びます。
 Objective-C では特に継承したいクラスがない場合には NSObject をスーパークラスとして指定します。Objective-C では継承できる親クラスは1つだけです。そしてすべてのクラスの親クラスを辿っていけば必ず NSObject に到達するようになっています。NSObject にはスーパークラスは存在しませせん。このようにスーパークラスのないクラスのことをルートクラスと呼びますが Objective-C では NSObject が唯一のルートクラスです。言語の仕様としてはルートクラスを自作することはできますが Apple は激しくそういうことをしないように注意しています。
 NSObject にはオブジェクトとして振る舞うための色々な設定がなされています。その代表的なものがクラスをインスタンス化する alloc メソッドでしょう。これに対してインスタンスを破棄するメソッドが dealloc ですが、こちらはコードの中で直接呼び出してはいけないメソッドの中の1つになっています。


オブジェクトのカプセル化
 ここまでオブジェクトの概要を説明してきましたが、object.m のソースコードを見ていて一番ウンザリするのがクラス定義(宣言)の部分でしょう。構造体では、

 typedef struct addressBook {
   char name[MAXLENGTH];
   char address[MAXLENGTH];
   char phoneNumber[MAXLENGTH];
   char email[MAXLENGTH];
 } addressBook ;

と6行で済んでいましたが、今回は同じデータ量を保持するオブジェクトを定義するのに50行も書いています。object.m の6行目から11行目までの { } で囲まれた4行の NSString *変数名; が addressBook 構造体の各メンバ変数に該当します。NSString は Objective-C で文字列を扱うクラスです。アドレスブックの各データ(文字列)を格納するためにこのNSString型の変数を宣言しています。アスタリスク * が付いているようにこの変数はポインターです。Objective-C ではオブジェクトはすべてポインターとして扱います。従ってオブジェクトの変数の宣言や引数の型としてオブジェクトを宣言するときには常に * が付きます。ただしオブジェクトなら種類を問わず何でも格納できる id という型があります。この型には * は付きません。しかしそれは表記上 * を付けないだけで内部ではポインターとして扱われます。なお C 言語の構造体ではメンバ変数と呼んでいたこの変数のことを Objective-C ではインスタンス変数と呼びます。インスタンス変数の名前はアルファベットの小文字で始めます。

 object.m の12行目から19行目までは先頭に - の付いた式 ( ; で終っている) が8行続いています。これは各インスタンス変数の値を設定するメソッドと各インスタンス変数の値を取得するメソッドです。メソッドとはそのオブジェクトに付随した関数と考えてもらって結構です。この8行には先頭に - が付いています。- ではじまるメソッドはインスタンスに対して使えるインスタンスメソッドであることを表しています。それに対して + ではじまるメソッドはクラスに対して使えるメソッドでクラスメソッドと呼ばれています。インスタンス変数の値を設定するメソッドを setter 、値を取得するメソッドを getter と呼び、両方を合せてアクセサメソッドもしくはたんにアクセサと呼びます。
セッターは setインスタンス変数名 をメソッド名にすることに決まっています。そして単語としての切れ目となるインスタンス変数名の最初の文字だけ大文字にします。
ゲッターは インスタンス変数名 をそのままメソッド名にすることに決まっています。先頭文字も小文字のままです。返り値や引数などは自由に設定できます。

 ここからが本題になります。構造体ではメンバ変数にどこからでも自由にアクセスできます。つまりどこからでも値の変更が可能です。しかしそれではマズイ面もあります。例えばメンバ変数がある人の各科目のテストの点数を表すものだとします。通常テストの点数と言えば 0 〜 100 点だと思います。しかし何のガードもなければ -20 点や 120 点という値も設定できてしまいます。間違って設定してしまう場合もあるでしょう。それに対して Objective-C ではインスタンス変数には直接アクセスできません。インスタンス変数が属しているオブジェクトのアクセサメソッドを通してしかアクセスできません。テストの点数を設定したいときにはそのデータが属しているオブジェクトのセッターというアクセサメソッドを設定したい値を引数として呼び出すしか方法がありません(正確にはObjective-C の場合にはメソッドを呼び出すのではなく、オブジェクトにメッセージを送ると言いますがここではあまり気にしないことにします)。メソッドの呼び出しはプログラムのどこからでも可能です。 値を受け取ったセッターメソッドはそれが 0 〜 100 点の範囲内にあれば該当するインスタンス変数に値を格納します。
 Apple ではこの方法が非常に有効であると判断してすべてのインスタンス変数はアクセサメソッドを通してしかアクセスできないことに決めました (正確には他の方法も用意はされています) 。そしてこのように内部データを守ることをオブジェクトのカプセル化隠蔽化と呼びます。

 しかしオブジェクトのカプセル化や隠蔽化はそのデータ(インスタンス変数)に対してのみ行われるものではありません。object.m の12行目から19行目は C 言語でいうところの関数プロトタイプを宣言しているのと同じことだと思ってください。そしてその関数が実際に行うことの定義(実装)は22行目の @implementation に続く23行目から54行目で行われています。今回の場合は自作クラスなのでこのように @implementation (実装) も私たちの目に見えていますが、Objective-C に最初から用意されているクラスについてはヘッダーは公開されていますが実装は隠されていると言えます。
 例えばセッターメソッドは引数として受け取った得点が -20 の場合にはそれを 0 点としてインスタンス変数に格納するようにしても良いです。しかしダイアログを表示してユーザーに間違った得点が入力されたことを告げて再入力を促すようにしても良いです。どちらの方法を実装するかはそのクラスを作った人の自由です。言い方を変えればそのクラスを使う人の知る必要のないことです。このようにオブジェクトのカプセル化・隠蔽化とはデータに対してもメソッドに対しても有効なことです。
 オブジェクトを使う人(プログラマ)が知らなければならないのはオブジェクトの内部構造ではなくて用意されているメソッドの使い方だけです。テレビを見るのにテレビの内部構造を知る必要はありません。知らなければならないのはテレビのリモコンの使い方だけです。

 このようにしてプログラミングをしていくとオブジェクトは一種の便利な箱となり、その箱に付いているどれかのボタンを押せば何かの仕事をしてくれます。このような感覚に段々と慣れてくるとオブジェクト指向以外でプログラミングすることが非常に面倒なものに感じてくることがあります。


メッセージ式    2008/4/22 追加
 ここまで書いて3日たってからとても大事なことを思い出しました。それは Objecitive-C でのメソッドの呼び出し方法についてまったく説明していなかったことです。すみませんでした。

 C 言語をオブジェクト指向に拡張した C++ 言語ではオブジェクトの各メンバ (メンバ変数もあればメンバ関数の場合もあります) にアクセスしたり呼び出したりするには
  オブジェクト名 . メンバ名
と記述します。このドット演算子 . を使う方法はその後のほとんどのオブジェクト指向プログラミング言語で採用されています。正確には C++ 言語ではドット演算子 . で繋ぐ場合は非ポインターの場合だけですが、その後に登場してきたオブジェクト指向言語ではポインターの場合でも . で呼び出せるように変えています。

 ところが Objective-C ではオブジェクトのメソッド ( C++ 風に言えばオブジェクトのメンバ関数 ) の呼び出しには

  [ オブジェクト名 メソッド名 ]

と角括弧で囲んだ メッセージ式 と呼ばれるものを使います。角括弧の左側にはオブジェクト名を、そして右側にはメッセージ (実質的にはメソッド名になります)を記述します。 Objective-C は C 言語に世界初のオブジェクト指向言語である smalltalk の機能を搭載したものです。このメッセージ式は smalltalk からの伝統であると同時に他のオブジェクト指向言語でのメンバ関数(メソッド)呼び出しとは根本的に違うところがあります。

 他のオブジェクト指向言語ではどのオブジェクトのどのメソッドを呼び出すのか、そしてそのオブジェクトとメソッドは確かに存在しているのかをコンパイル時に検証してその結びつきを決定してしまいます。このことを静的バインディング(変更できない結びつき)と呼びます。それに対して Objective-C では正確にはオブジェクトのメソッドを呼び出すのではなくオブジェクトにメッセージを送っているだけです。メッセージの送り先のオブジェクトが確かに存在しているかどうかはコンパイル時にチェックされることはありません。送り先のチェックさえしないわけですから送り先がメッセージに応えられるかどうか(そのメソッドが存在しているかどうか)などはさらにチャックできるわけがありません。こういう結びつきを動的バインディングと呼びます。動的バイディングではそのプログラムが正常に動作するかどうかはプログラムを実際に動かす時まで分からない部分もあります。このような動的バインディングには柔軟なプログラミングができるというメリットと、オーバーヘッド(CPUの負荷)が大きくなるというデメリットがあります。なお最近の Apple の指針ではは特に動的バインディングを利用しないプログラムの場合にはメッセージ式の結果(返り値)を受ける変数には明確な型指定をするように推奨しています。これはソースの可読性の向上を計る意味と静的バインディング(結びつき)のチェックをコンパイル時に行えるようにして安全性を計るという意味の両方があるように思います。

 なお Objective-C の動的バイディングを利用すれば存在しないオブジェクトに存在しないメソッド名をメッセージとして送ってもコンパイル時にエラーは出ないと書かれている書籍もありますが、これは間違いです。確かに smalltalk の時には上記のようなこともあったかもしれませんが、現在の Objecitive-C ではそこまでのことはありません。実際に色々と試してみるとある程度の線でエラーが出ることが分かると思います。

メッセージ式のネスト

メッセージ式はネスト(入れ子)することができます。
object.m の61行目では
[[NSAutoreleasePool alloc] init] となっています。これは
[NSAutoreleasePool alloc] で NSAutoreleasePllo に alloc クラスメソッドを送ってインスタンス化して
[NSAutoreleasePool のインスタンス init] と init インスタンスメソッドで初期化するという2行をネストして1行にしています。

Objective-C メソッドの引数の記述について

 Objecitive-C ではメソッドの引数の記述方法もかなり変わっています。
例えば矩形(四角形)の横と縦のサイズを設定する C 言語の関数のシグネチャは
  void setRectangleSize(int width, int height)
となると思います。これを Objective-C でのメソッドのシグネチャ風に書き換えれば
  - (void) setRectangleWidth:(int)width Height:(int)height
となります。
 まず先頭にクラスメソッドであるかインスタンスメソッドであるかを表す + か - を付けます。次に返り値や引数の型は( )で囲みます。引数の前はコロン : で区切ります。複数の引数がある場合はそれぞれの引数にラベルというものを付けることが出来ます。上記の例では setRectangleWidth:(int)width Height:(int)height の Height の部分がラベルにあたります。このラベルを付けることによってその引数が何の値かが分かりやすくなりますがメソッド名全体が長くなるというデメリットもあります。
 ラベルをなくした
  - (void) setRectangleWidth:(int)width:(int)height

  - (void) setRectangleWidth:(int)width Height:(int)height
は別のメソッドとして区別されます。





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

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

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