オブジェクトを作ろう - Cocoaプログラミング

自分でクラスを設計し、インスタンスを生成してみましょう。

てくっち

自前のクラスを設計して、使ってみる練習をしますよ!

てくっち

Humanクラスを設計して、和田インスタンスを生成してみます。

てくっち

まずXcodeから、新規プロジェクトを

てくっち

てくっち

このように、Foundation Toolというのを選んでください。

てくっち

説明を訳すと「このプロジェクトではFoundationライブラリにリンクするコマンドラインツールを作成します」

てくっち

GUIがないのです。なのでインターフェースビルダーは使いません。

てくっち

プロジェクト名は「ObjectEX」にしましょう。オブジェクト・エクササイズ。

てくっち

てくっち

ObjectEX.mを編集しますー。

和田

すいません先生・・・。和田は今日寝てました・・・。

てくっち

おはようございますー

てくっち

じゃあいきなりビルド&実行してみましょう。

てくっち

どんなんでましたか!

和田

ビルドしましたが何もでてきませんでした…。

てくっち

コンソールに何か出力されていますよう!

てくっち

メニューの実行>コンソール

和田

和田

どこにあるんすかそれw

和田

あ!あった!

和田

[Session started at 2009-02-24 18:32:01 +0900.]
2009-02-24 18:32:01.655 ObjectEX[411:10b] Hello, World!

The Debugger has exited with status 0.

和田

と表示されました!

てくっち

Hello, World!が出てますねー

和田

でてますよっ!

和田

らくちんですねこれ!

てくっち

これは、ソースファイルの、NSLog関数の働きなのです

てくっち

NSLogは、実際の開発で、デバッグに大活躍しますよー

和田

syslogと同じ感じですかね!

和田

そういえばRSSつきましたよ!

てくっち

てくっち

すごいっす

和田

ぜひ!

和田

おつかれさまっした!

てくっち

さて!

てくっち

Humanクラスを作りますよ

てくっち

てくっち

新規ファイル...からこのようにObjective-C classを選んでください。

てくっち

てくっち

そして、ファイル名をHuman.mに。.h(ヘッダファイル)も同時に作成。

てくっち

てくっち

こんな感じでファイルを整理しておきます。

てくっち

ちなみにXcode上で見えるグループとファイルでのフォルダ階層は、実際のフォルダ階層とは違ってて

てくっち

てくっち

プロジェクトのフォルダをFinderで見ると、ほらこの通り。

てくっち

Xcode上で見えているファイル構成は、作業がやりやすいようにXcodeが独自に気を利かせてくれているんです。

てくっち

将来ソースファイルの数が増えてきたときに、自分の好みで新規フォルダ(グループ)を作って整理できますー。

てくっち

さてHumanクラスの設計です。

てくっち

名前と年齢を持つことにしましょう。名前は文字列。年齢は整数で。

てくっち

Human.hを次のようにしましょう。

てくっち

#import <Foundation/Foundation.h>


@interface Human : NSObject {
  int age;
  NSString *name;
}

- (int)age;
- (void)setAge:(int)newAge;

- (NSString *)name;
- (void)setName:(NSString *)newName;

- (void)sayHello;
@end

てくっち

importするものを、CocoaからFoundationに変えています。

てくっち

Cocoaフレームワーク

てくっち

は、3つのフレームワークからなっていて、

てくっち

Foundationフレームワーク AppKitフレームワーク CoreDataフレームワーク、なんです

てくっち

ためしにspotlightでCocoa.hを探して、開いてみてくださいな。

てくっち

#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import <CoreData/CoreData.h>

てくっち

ほら、3つimportしてるでしょ。

てくっち

FoundationはCocoaでもちいる基本的なデータを表現するクラス群。文字列とか配列とか。

てくっち

AppKitはユーザーインターフェースに関するクラス群。ウインドウとかテキストフィールドとか。

てくっち

CoreDataはデータ設計を便利にしてくれるクラス群です。

てくっち

さて今回の練習ではFoundationフレームワークだけでいいわけです。

てくっち

不要なフレームワークのimportを省くことで、ビルドもちょっと早くなるし、なにより

てくっち

他の環境へ使い回せる可能性が広がります。

てくっち

iPhoneではちなみにFoundationフレームワークがそのまま使えるので

てくっち

今回のHumanクラスは、iPhoneアプリでもそのまま使えます。

てくっち

なんかうれしいでしょ。

てくっち

@interface Human : NSObject {
  int age;
  NSString *name;
}

てくっち

さてこの{ }の中で、インスタンス変数を宣言しています。

てくっち

int型(整数)wo

てくっち

int型(整数)を1つ、NSString(文字列)を1つ。

和田

早起き!

和田

ってか仕事しろって感じですね先生!

てくっち

あ、おはようございます!

和田

おはようございますw

てくっち

どんどん進めます!w

和田

おざっす!

和田

ちょっと上から見返してますね!

てくっち

はーい

和田

Humanを追加してソース書きましたよっ!

和田

やっぱこの形式、わかりやすくていいですね(笑

てくっち

NSStringのうしろに*があるのは、深い意味がありまして

和田

ポインターですか!?

てくっち

ええ。変数nameには、文字列インスタンスへのポインタが格納されるっちゅうことなんですよ。

てくっち

この*,

てくっち

最初はよく忘れるんですよねw

和田

そしてポインタの意味をよく理解していない和田に説明を…。

てくっち

えっと、メモリのアドレスのことです

てくっち

インスタンスが格納されているメモリの先頭アドレス。

和田

文字列インスタンス…へのポインタ…。

和田

ってことはインスタンスはメモリ上に展開されたクラスですよね?

てくっち

そうですー

和田

つまり展開された後の場所を示すってことですか?

てくっち

そうですねー

てくっち

プログラム内でインスタンスを指し示すのは、こういう方法を使うんです。

和田

ふむふむ

和田

わかったようなわからないような…。

てくっち

とりあえずクラスに対しては、このように*を使うということで、進めましょう!

和田

はい!

てくっち

- (int)age;
- (void)setAge:(int)newAge;

- (NSString *)name;
- (void)setName:(NSString *)newName;

- (void)sayHello;

てくっち

あとは、メソッドの宣言です。

てくっち

全部で5つのメソッドを宣言してみました。

和田

変数もメソッドって言うのですか?

てくっち

えっと、このageとかnameとかも、メソッドですね

てくっち

わざと変数と同じ名前にしたメソッド。

和田

和田

変数とメソッドが同じ名称ってことですか!?

てくっち

そうですー

てくっち

以前、テレビのたとえ話をしましたが

てくっち

インスタンス変数は、基本的に企業秘密であって、

和田

はい

てくっち

それを利用者にアクセスさせるかどうかは、このメソッドで決めるわけです

和田

ふむふむ

てくっち

setAgeとsetNameは、変数を書き換えるメソッドなんですが、

てくっち

もし利用者に変数を設定してほしくなかったら、このメソッドを宣言しなければいいんです。

和田

ふむぅ?

てくっち

つまり読み出し専用の変数になるということですね

和田

ユーザエンド側のメソッドとしてsetAgeとsetNameを宣言してるっつーことですよね?

てくっち

そうです

和田

それゆえポインタが必要になるってことですよね?

てくっち

どれゆえかしら…

和田

忘れてくださいw

てくっち

では進めますw

和田

あ!もういっこ!

てくっち

はいー

和田

@interface Human : NSObject {
  int v_age;
  NSString *name;
}

- (int)m_age;

和田

こういうのは成立するんですか?

和田

変数とメソッドの名前がちがくてもOKでしょうか?

てくっち

OKですが

てくっち

変数と同じ名前のメソッドというのは、実は深い規則がありまして!

和田

てくっち

変数xに対して、メソッドxとsetX

和田

定石!?

てくっち

があることを前提にした仕掛けが、あるんでやんす

てくっち

文字列でメソッドを呼び出すしくみがあってね、

和田

なるほど…。了解デス!

てくっち

退屈なコードをごっそり減らせるありがたいしくみなんですが

和田

でも、どれが変数でどれがメソッドかわからなくなるとかいう心配は無いのでしょうか…。

和田

あ、そうか。いやいや、すいません。

てくっち

文法上、使い方が違いますよね。

和田

へいw

和田

まだその文法がよく理解できてないのでよくわからなく見えてるのですね和田はw

てくっち

すぐ慣れると思いますー

和田

ブラジャー!

てくっち

さてsetAgeとsetNameには、うしろに引数が付いていますね

和田

はい!

和田

(int)newAge

和田

(NSString *)newName

和田

ですね!

てくっち

いえ違います・・

和田

orz

和田

吹きましたw

和田

自信満々だったのに…orz

てくっち

あ、そうでしたw

てくっち

すみませんすみませんw

和田

えええぇえぇええええぇぇぇぇぇ!w

てくっち

()の中は引数の型で、newなんとかは適当につけた名前です。

和田

これも慣例でメソッドと同じ名称にしてるってわけですねっ!

てくっち

そうですね

てくっち

xとかstringとかつけるより、意味がわかるでしょ

和田

はいっ!

てくっち

で、メソッド名の前にある()には、メソッドの返り値の型を書きます。

和田

int型とNSString型ってことですね

和田

整数と文字列

てくっち

ええ

てくっち

ここでもNSStringの後ろに*を忘れないでくださいね。

てくっち

ポインタが返ってくるということですね。

てくっち

ポインタが返ってくるということですね。

和田

2回言いましたね。

てくっち

うふ

和田

ちょっと外出してきまふっ!

てくっち

はーい

てくっち

さて今度は実装部です。Human.m

てくっち

@implementation Human

@end

てくっち

この2行の間に、メソッドを実装していきます。

てくっち

まずはageメソッド

てくっち

- (int)age
{
  return age;
}

てくっち

インスタンス変数ageを返すだけ。

てくっち

そしてsetAgeメソッド

てくっち

- (void)setAge:(int)newAge
{
  age = newAge;
}

てくっち

これも簡単で、インスタンス変数ageを、引数newAgeの値に置き換えます。それまでのageの値は消えます。

てくっち

これでインスタンス変数ageの読み書きができるようになりました。

和田

追加しましたぜ

てくっち

つぎにnameについて、

てくっち

- (NSString *)name
{
  return name;
}

- (void)setName:(NSString *)newName
{
  [newName retain];
  [name release];
  name = newName;
}

てくっち

setNameはなんかややこしい!

てくっち

retainやらreleaseやら、詳しい解説は別テーマでやりましょうかね・・・

和田

retain

和田

ってなんすか

てくっち

「このインスタンスは俺が面倒を見るぞ!」という感じですね

てくっち

releaseはその逆。

和田

なんかすごいわかりやすい感じですねw

てくっち

で、インスタンスはみんな、「何人に面倒を見られているか」カウンタを持っているんです。

てくっち

参照カウンタと言って、インスタンス変数の1つですね。

和田

ほほぅ!

てくっち

「誰に面倒を見られているか」は残念ながらわかりません。

てくっち

プログラマ自身がわからなくなることもありますw

和田

リリースは解放ってことですか?

てくっち

そうです

和田

retainってなんて読むんですかねぇ

てくっち

インスタンスが誕生したとき、参照カウンタは1

てくっち

リテイン、ですね

てくっち

保持

和田

リゲインみたいですね。

てくっち

ええ。似た意味ですね

和田

似た意味なんすかw

てくっち

参照カウンタは、retainされると 1、releaseされると-1

和田

ふむふむ

てくっち

(あ、プラスが表示されねえ!)

和田

基本的には使った後は解放するんですよね?

てくっち

そうです

てくっち

もういらなくなったのに解放されないと、メモリのムダです。

てくっち

参照カウンタが0になったとき、そのインスタンスは消滅します。

和田

さっきの処理ですが

てくっち

はい

和田

普通に変数に変数を上書きってわけにはいかんのですか?

和田

  [newName retain];
  [name release];
  name = newName;

てくっち

いい質問だす!

和田

あら!

てくっち

単に上書きをするとですね、

てくっち

もとのnameが指していた文字列インスタンスが、誰からも参照できなくなりますよね。

てくっち

だから[name release]が必要なんですわ。

和田

なるほど!

和田

ここでポインタさんの話がでてくるってことですね!

てくっち

そうです

てくっち

変数に納まっているのは、ポインタなので、

てくっち

それを意識してもらえばいけると思います!

てくっち

で、先にnewNameをretainしている理由は、

てくっち

万が一nameとnewNameが同じ文字列を指していた場合の対策です。

てくっち

詳しく説明してしまいましたね!

和田

先生!

和田

nameには文字列が入ってるんですよね?

てくっち

文字列インスタンスのポインタ、ですね

和田

和田憲幸みたいなのが入ってるのではなくて

てくっち

文字列は、はいってないっす!

和田

あくまでポインタなんですね

てくっち

そうです。インスタンスを扱うのはいつもこんな感じなんです。

和田

変数型はNSStringなんですか?

てくっち

そうですね、NSStringクラスですね

和田

ポインタはNSStringに入れるんですか?

てくっち

NSString *a;

てくっち

と宣言してから、ani

てくっち

aに入れます。

和田

ふむふむ

和田

int *a;

和田

でもよいんですか?

てくっち

それもアリです

てくっち

aには、「int型の値へのポインタ」が入れられますよ

和田

???????

てくっち

巷で難解だという評判の、ポインタの話ですねw

和田

その場合の値ってのはどこにあるんですか???

てくっち

メモリ上の、どこか。

てくっち

そのアドレスが、aに格納されます。

和田

その値の場所を示すのがポインタ???

和田

直接、変数aに値が入ってちゃ駄目なんですか!?

てくっち

それはint a;と宣言したときの場合ですね。

てくっち

*の違いが大きな違いです。

和田

ポインタはどういう場面で使うものなんでしょうか…

てくっち

とりあえずは、今回の例で、使ってますよね

てくっち

いまのところはこれだけでいいかとw

てくっち

Cの本を読むと、もっとすんげえポインタの活用が載ってて、おしっこちびりますけどね

和田

ちびびっ!

てくっち

さてこれでインスタンス変数ageとnameの読み書きをするメソッドを実装しました!

てくっち

こういうメソッドを、アクセサメソッドといいますー

てくっち

accessor

和田

あっ!臭ぇ!サリー!!!!

和田

先生!ガベージコレクションってのは!?

てくっち

それは、Objective-C 2.0で新登場した機能で、

てくっち

「誰からも参照されなくなったインスタンスを自動的に見つけて殺してくれる」機能です。

和田

てくっち

この機能をONにしていると、releaseしてやる必要がないっす。

和田

殺し屋???

てくっち

ええ

てくっち

しかし、現在iPhone開発環境ではガベージコレクション、使えないんじゃないかなー

和田

変数はいちど展開してしまうと、その変数自体に手を加える事はできないと。

和田

そうおっしゃりたいわけですか

てくっち

いや、iPhoneアプリ開発では、注意してretain, releaseして面倒を見なきゃ、てことです。

和田

あ…すいません…ポインタの話をしていましたw

和田

PHP、Javascript、Perlで言うとこのローカル変数って事なのかなぁ

てくっち

ローカル変数・・うーむ・・・

てくっち

さ、さて、もうちょっとメソッドを追加します!

てくっち

- (void)sayHello
{
  NSLog(@"こんにちは。%@です。%d歳です。よろしくね。", name, age);
}

てくっち

sayHelloメソッドです。

てくっち

やっとインスタンス変数の利用例を紹介できましたね。

和田

sayHelloメソッド

和田

っつーのはもう用意されてるメソッドなんですか??

てくっち

いえ、俺たちのオリジナルですw

てくっち

.hファイルで、宣言したでしょ

和田

で、ですよねw

てくっち

このように、好きにメソッドを作りまくるわけです。

和田

ふむふむ

和田

あ!で

和田

これがコンソールに表示されるってことですね!

てくっち

そうですー

和田

見えてきたかも!

てくっち

@"..."の文字列が表示されますよ。

和田

@"文字列"

和田

は文法なんですよね?

てくっち

ええ。これ自身、文字列インスタンスになります。

てくっち

@を付けないと、伝統的なCの文字列になってしまいます。

和田

普通のC言語は普通に char

てくっち

Objective-CはCの拡張版なので、Cのソースがそのまま実行できるようになっているんす。

和田

和田

あう…

和田

ふむふむ

和田

たとえば改行は

和田

¥nですか?

てくっち

そうです

てくっち

それから、イニシャライザ。

てくっち

- (id)init
{
  [super init];
  age = 0;
  name = @"no name";
  NSLog(@"Humanインスタンスを1つ生成しました。名前は%@、年齢は%d。", name, age);
  return self;
}

てくっち

これは、インスタンスが誕生したときに、最初に呼ばれるメソッドです。

てくっち

ややこしいですが、これは避けて通れないわよ!

和田

初期設定ですね

てくっち

ええ

てくっち

そして、最後にこれ。

和田

javascriptにonloadという似たようなメソッドあります

てくっち

おお

てくっち

- (void)dealloc
{
  NSLog(@"%@は消滅します。あばよ!", name);
  [name release];
  [super dealloc];
}

てくっち

これはインスタンスが消滅する直前に、呼ばれます。

和田

onunloadメソッドですね!

てくっち

同じ働きのあるんですね

和田

へいっ!

てくっち

これでHumanクラスは完成です!

和田

人間にはほど遠いですね!

てくっち

名前と年齢だけですからねえw

てくっち

けどもうちょっと機能を追加すれば、RPGのキャラクタぐらいにはなるでしょう。

和田

UIがないだけでだいぶ理解しやすい感じがしますね〜

てくっち

専念できるとわかりやすいですね

てくっち

さて、いよいよメインルーチンです。これがほんとに最後ですw

和田

てくっち

ObjectEX.mを次のように!

てくっち

#import <Foundation/Foundation.h>
#import "Human.h"

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

  Human *wada = [[Human alloc] init];  //和田さん誕生!
  [wada sayHello];    //しかし名前はまだない
  [wada setAge:20];    //年齢を設定
  [wada setName:@"WADA"];  //名前を設定
  [wada sayHello];    //ごあいさつ
  [wada release];      //和田さん消滅!
  
    [pool drain];
    return 0;
}

てくっち

和田インスタンスの一生です。

てくっち

ビルドして実行してください!

和田


[Session started at 2009-02-25 15:51:04 +0900.]
2009-02-25 15:51:04.872 ObjectEX[8618:10b] こんにちは。(null)です。0歳です。よろしくね。
2009-02-25 15:51:04.873 ObjectEX[8618:10b] こんにちは。WADAです。20歳です。よろしくね。

The Debugger has exited with status 0.

和田

こんなんでました

てくっち

あれ、initとdealloc内のNSLogが呼ばれてない…

てくっち

[Session started at 2009-02-25 15:56:03 +0900.]
2009-02-25 15:56:03.879 ObjectEX[1076:10b] Humanインスタンスを1つ生成しました。名前はno name、年齢は0。
2009-02-25 15:56:03.885 ObjectEX[1076:10b] こんにちは。no nameです。0歳です。よろしくね。
2009-02-25 15:56:03.886 ObjectEX[1076:10b] こんにちは。WADAです。20歳です。よろしくね。
2009-02-25 15:56:03.887 ObjectEX[1076:10b] WADAは消滅します。あばよ!

The Debugger has exited with status 0.

てくっち

うちではこうなります。

和田

コピー忘れですね…。

てくっち

メッ!

てくっち

Humanインスタンス和田の使い方、わかってもらえたでしょうか!

てくっち

[wada sayHello];

てくっち

つまり[インスタンス名 メソッド名] ですね

てくっち

wadaインスタンスに、sayHelloメッセージを送る、といいます

てくっち

つまり、このwadaインスタンスのsayHelloメソッドが呼び出されるわけですね。

てくっち

年齢と名前のデータをどのように処理するかは、wadaインスタンスに任せとけばいいんです。

和田

ふむふむ

和田

各インスタンスごとが独立してるってことですよね

和田

独立ってか隔絶?

和田

縦割り行政みたいなもんでしょうか

和田

でポインタという内線をつかって参照することはできまっせと。

てくっち

そうですねー。役割分担が明確ですね。

てくっち

今回は、盛り込み過ぎましたかね……

和田

ステップを分けた方が…

和田

オブジェクトを作ろう(1)とかw

てくっち

むぅ!

てくっち

説明したいことはまだ山のようにあるのに!

てくっち

よーしでは続きは別のテーマで!

和田

おざっす!

和田

本日もありがとうございますた!