このFAQの著者はGraham Leeです。 この文書に対するコメントと訂正は彼に送ってください。この文書を執筆するに あたっては、comp.lang.objective-cの 人達の計り知れないほど貴重な手助けがありました。とくにMichael Ashには、 カテゴリ、プロトコル、スレッド同期のセクションの執筆と、セレクタと 実装ポインタのセクションについて多くのアドバイスをもらいました。
そのとおり、すでにあります。Devid Stesがメインテナンスしている公式FAQが rtfm.mit.edu にあります。しかし、そのFAQの内容は古く、GNUとAppleのObjective-Cに関する セクションに多くの誤りや取り上げていない情報があります。そこで私は、Davidがまだ 更新していないFAQに最新の情報を提供することにしました。このFAQは、オリジナル のFAQに代わって、広範囲にわたる正確な最新情報を提供することを目指しています。 できれば、このFAQがcomp.lang.objective-cに おける、より完全で正確なFAQになればよいと思っています。 最近、私はこのFAQからPOCに特有な情報を除くことにしました。というのは、 このUsenetグループには、POCに関心がある人はほとんどいないように思えたからです。 POCに関心がある人は、今でも、前述の公式FAQから情報を得ることができます。 このFAQでは、gcc版のObjective-Cのサポートに集中し、もっと役に立つ、最新のFAQの メインテナンスをしたいと思っています。
今、あなたが読んでいるFAQは、2007年2月14日に更新されたものです。 最新版は、thaesofereode.infoで みつけることができるでしょう。また、このFAQの公開場所のお知らせが、毎月 comp.lang.objective-cに投稿されます。
この文書は原著者の許可を得て翻訳、公開しています。
著者: Graham Lee
原文: The comp.lang.objective-C FAQ listing
日本語訳: 神宮信太郎(jin@libjingu.jp)
日本語訳に関するコメント、誤りの指摘などありましたら、訳者までお知らせください。
日本語訳にあたり、以下の文献を参考にさせていただきました。
Objective-Cは1980年代の始めごろ、Brad Cox博士が考案しました。
Objective-Cは、人気のあるC言語の上に作られた、オブジェクト指向を
実現する層として開発されました(このFAQとその他のObjective-Cに関する多くの
文書では、C言語またはそれに近い言語の知識があることを前提とします)。
Cox博士が実装したオリジナルのObjective-Cは、Cコードを生成するプリプロセッサ
として実現され、Stepstoneの製品として販売されました。
Objective-Cは、C言語の純粋なスーパーセットです。そのため、コンパイラが
サポートするC言語のバージョンによって、Objective-Cは変化することがあります。
C言語に追加された機能はわずかで、ほんの少しの新しいキーワード、3つの型(そのうち
のid
だけが最もよく使われる)、そしてメッセージ送信の構文が
追加されました。
Objective-Cには、C、C++、その他の言語にあるような標準規格の文書はありません。 したがって、Objective-Cの公式な定義はその実装によって示されます。
最も普及していて、活発に開発されているObjective-Cは、Appleがメインテナンス している、GNU Cコンパイラをベースにしたものです。 Free Software Foundationによる通常のGNU CコンパイラもObjective-Cをサポート しており、その実装はAppleのものととてもよく似ていますが、新機能の追加に 少々時間がかかります。また、両者のランタイムには互換性がありません。 David StesによるPOCというObjective-Cの実装もありますが、前述のふたつとは 互換性がなく、AppleのObjective-Cが提供している機能の多くを含みません。 しかしその一方で、POCはブロック(Smalltalkの機能。Rubyにもみられる)と クラス変数をサポートしています。POCは、他の普及しているObjective-Cと互換性が ないため、このFAQではこれ以上取り上げません。
Appleは、Mac OS Xバージョン10.5("Leopard")において、Objective-Cに いくつかの追加の機能を導入しています。これらの機能は"Objective-C 2.0"と して登場する予定です。ここではこれをObjC 2と呼ぶことにします。ObjC 2に ついては、Appleが公式にアナウンスするときに明らかになるでしょう。 後述するObjC 2のセクションを参照してください。また、ObjC 2に関する議論の中で、 さらなる検討を必要とするところでは、このフォントスタイルを 使って記述します。今のところ、ObjC 2の機能は、OS X 10.5のデベロッパ プレビューに含まれるApple版のランタイムでのみ利用できます。新しい機能を使用する かどうかは自由に選択でき、既存のObjective-Cとの互換性は失われません。
Objective-Cのソースコードは、一般的に、CやC++のソースコードと同じように 構成します。宣言とオブジェクトのインタフェイスは、拡張子.hを付けたヘッダ ファイルに記述し、定義とオブジェクトの実装は、拡張子.m(メソッド[method]の略)を 付けたファイルに記述します。
ほとんどのプラットフォームで利用できるgccを Objective-Cコンパイラとして利用できます(gccには2種類ありますが、それについては 後述します)。歴史的には、NeXTコンピュータ(後のNeXTソフトウェア)とStepstoneが Objective-Cコンパイラを製作しました。しかし、どちらの会社もすでにありません。 NeXTがStepstoneからObjective-Cに関する権利を取得し、その後、AppleがNeXTの 権利を取得したことにより、現在では、Appleコンピュータが所有権を持っています。 Metrowerksの製品であるCodeWarriorという開発環境がObjective-Cコンパイラを 備えていましたが、こちらも今では存在しません。
Objective-Cのソースファイルは、普通のテキストエディタを使用して編集できます。 また、EmacsのようなObjective-Cの構文をハイライトするモードがあるエディタが あります。Objective-Cをサポートする統合開発環境には、Appleの Xcodeや GNUstepの ProjectCenter といったものがあります。
Objective-Cコンパイラがあれば、すぐにプログラミングを始めることはできますが、 それだけでは、自分が関心を持っている対象のプログラミングをする前に、多くの 車輪の再発明をしなければならないでしょう。クラスライブラリは、事前に作成された オブジェクトとクラスのセットを提供します。クラスライブラリは通常、コンテナ、 データ処理、ユーザインタフェイスといった機能を提供します。
Objective-Cで利用できるクラスライブラリの多くは、NeXTコンピュータと
Sunマイクロシステムズが開発したOpenStepに由来します。これらには、Apple
Mac OS XのCocoa、
クロスプラットフォームのGNUstepといった
ものがあります。どちらのクラスライブラリも、それぞれに特有なクラスの他に、
ファンデーション(コンテナ、文字列、ネットワーク/IPCなど)、AppKit(ユーザ
インタフェイスコンポーネント)を提供します。
OpenDarwinのlibFoundationは
ファンデーション部分のみ実装しています。
Swarmという
エージェントベースモデリングのためのObjective-Cフレームワークがあります。
#import
というのはどういうものですか#import
は、C言語の#include
とよく似た動作をする
プリプロセッサ指令です。#include
と異なり、#import
は
同じファイルを2度インクルードしません。これにより、ヘッダファイルの中で
#ifdef
を用いて2重インクルードを防止する必要はなくなります。
gccのバージョンによっては、#import
を使うと警告を出すものが
あります。その場合には、-Wno-import
オプションを指定すること
で警告を出力させないようにすることができます。
C言語と同じスタイルのコメントを使用できます。ひとつは、//
で
始まるコメントで、行末までがコメントになります。
// BCPLスタイルのコメント
もうひとつは、/*
で始め、*/
で終わるコメントです。
/* Cスタイルのコメント * 複数行にわたってコメントできる * この形式のコメントはネストできないことに注意! */
Objective-Cのキーワードid
は、任意のオブジェクトへの参照に
相当する型です。特定のクラスのポインタを使えば、より厳格な型チェックを行わせる
こともできます。例えば、MyClass *
には、MyClass
クラス、
または、そのサブクラスのオブジェクトへの参照を格納することができますが、その他
のクラスのオブジェクトへの参照を格納しようとするとエラーになります。
self
, super
とは何ですかself
は、メソッドの実装の中で利用できる特別な変数で、現在の
オブジェクト(すなわち、メソッドを起動したメッセージを受信したオブジェクト)への
参照を格納しています。
super
は、スーパークラスに実装されているメソッドを起動するために
使用します。selfとsuperは一般的にオブジェクトの初期化で使用されます。
-(id)init { if((self=[super init])) { // スーパークラスの-initを起動する someIvar=[[MyClass alloc] init]; } return self; // オブジェクト自身を返す }
次のように、オブジェクトに対して-class
メッセージを送信すると、
オブジェクトのクラスを表すオブジェクトを返します。
MyClass *someObject=[[MyClass alloc] init]; id aClass=[someObject class];
クラスオブジェクトはルートクラスのインスタンスのように振る舞います(すなわち、
[aClass class]
はルートクラスを返します)。aClass
が
参照しているオブジェクトは、クラスオブジェクトMyClass
であり、
MyClass
にメッセージを送信したときと同じようにクラスメッセージに
応答します。例えば、[[aClass alloc] init]
はMyClass
の
新しいインスタンスを生成します。
nil
とは何ですかnil
はnullオブジェクトを表し、オブジェクトが未初期化である
ことや、クリアされた状態であることを表わすのに使用されます(また、Nilはnullクラス
オブジェクトを表します)。他の言語と異なり、Objective-Cでは、nilにメッセージを
送信することが許されています。そのときの返却値は、返却値の型がid型のメッセージ
の場合はnilを返し、intのような単純なC言語の型の場合は0を返します。structの
ような複合型の場合の返却値は未定義です。nilとNilはともに(id)0
と
定義されています。
@defs
とは何ですか@defs
キーワードは、与えられたクラスのオブジェクトのメモリ上に
おけるレイアウトと同じC言語の宣言のリストを用意します。これは、Objective-Cの
オブジェクトを操作するCのAPIを作成するのに使用できます。次のように使用します。
struct MyClass_t { @defs(MyClass) };
ライブラリの中での使用であれば問題ないのですが、一般的に@defs
の
使用はカプセル化に違反することになるので、好まれないことに注意してください。
手短に言うと、それはできません。(-(id)doSomething:(id)sender
の
ように)メッセージのパラメータにコンテキスト情報を含めればできるかもしれ
ませんが、メッセージ送信者が正確なコンテキスト情報を渡す必要があります。
その必要はありません。普通は、変更したクラスとそのサブクラスを再コンパイル
しなければなりません。@defs()
を使用しているCのソース(あるいは
インスタンス変数に直接アクセスしているソース)は、そのクラスのメモリ上の
レイアウトが変わるので、再コンパイルする必要があります。インスタンス変数に
アクセサメソッド、または、KVC/KVO(「キー値コーディング、キー値監視とはなん
ですか」を参照)を介してアクセスしているコードは、再コンパイルする必要は
ありません。
カテゴリは、既存のクラスにメソッドを追加する方法です。
カテゴリは、既存の@interface
と@implementation
宣言と
は別の宣言として記述され、そこには、カテゴリが実行時にロードされるときに
クラスに追加されるメソッドが含まれます。カテゴリは、gcc版のObjective-Cを
実装しているコンパイラでのみ利用できます。
カテゴリのインタフェイス宣言は次のように記述します。
@interface ClassName (CategoryName) -method; @end
カテゴリ名は実行時に何の影響も及ぼしませんが、同じクラスの他のカテゴリとは
異なる、一意な名前を付けなければなりません。また、カテゴリ名はスタックトレースに現れます。
カテゴリの実装は次のように記述します。
@implementation ClassName (CategoryName) -method {...} @end
カテゴリを使用すると、ひとつのクラスの実装を複数のファイルに分割することが
できます。クラスの実装をカテゴリに分割し、それらを別々のファイルに記述します。
また、カテゴリを使用すると、ソースコードを直接修正できないクラスにメソッドを
追加することもできます。たとえば、あなたが使用しているクラスライブラリに
含まれるあるデータコンテナに、-md5Sum
メソッドを追加するといった
ようなことができます。
簡単に言ってしまえば、プロトコルは実装を持たないインタフェイスです。 プロトコルは便宜上ひとつにまとめられたメッセージの集合です。プロトコルを使用 すると、あるクラスがプロトコルのすべてのメッセージを実装していると宣言する ことや、あるオブジェクトがそのプロトコルに適合しているかどうかを確認すること ができます。また、プロトコルを変数の型宣言に使用して、その変数が参照する オブジェクトが特定のメソッドの集合を持つが、そのオブジェクトの実際のクラスに ついては関心がないということを示すことができます。
プロトコルは次のように宣言します。
@protocol ProtocolName -method; @end
プロトコルは他のプロトコルから継承できます。プロトコルの継承は、クラスの 継承の場合と同じように、親プロトコルのメソッドが子プロトコルに追加されます。 プロトコルを継承する場合、宣言の1行目は次のようになります。
@protocol ProtocolName <ParentProtocol>
クラスのインタフェイス宣言を次のように記述します。
@interface ClassName <ProtocolName>
複数のプロトコルに適合させる場合、プロトコル名をカンマで区切って記述します。
デフォルトでは、次のようなクラスの場合、
@interface MyClass : NSObject <AProtocol>
gccは、AProtocol
プロトコルから継承したメソッドが実装されている
か調べるために、MyClass
のインタフェイスだけ調べます。そのため、
そのメソッドがスーパークラスで実装されていても、MyClass
にメソッド
がみつからない場合は警告します。gccに-Wno-protocol
オプションを
指定すると、gccはプロトコルメソッドをクラス階層の中から探すようになります。
Objectクラスの場合、次のように記述します。
[obj conformsTo:@protocol(ProtocolName)]
NSObjectクラスの場合、次のように記述します。
[obj conformsToProtocol:@protocol(ProtocolName)]
プロトコルに適合する変数で、クラスに制限がないものを宣言するには、次のように 記述します。
id <ProtocolName> obj;
特定のクラス(またはそのサブクラス)で、プロトコルに適合する変数を宣言する には、次のようにします。
ClassName <ProtocolName> *obj;
この形式を使用することはほとんどありません。
非形式プロトコルは実のところプロトコルではなく、メソッドの実装を持たない カテゴリのインタフェイス宣言です。非形式プロトコルはCocoaの中でよく使用され、 形式プロトコルの使用が適当でない状況で、コンパイラに対してメソッドの集合を 宣言するために使用されます。たとえば、実装する必要のないメソッドが多くある というような状況です。
SEL
とは何ですか。どのように取得するのですかSEL
は、メソッドセレクタを表わすCのデータ型です(すなわち、SEL型の
データは個々のメッセージを一意に識別します)。メソッドセレクタは、@selector()
キーワードを使用して取得できます。
SEL aSelector=@selector(doSomethingWithStuff:);
また、ランタイム関数を使用して取得することもできます。
Apple gccではsel_getUid()
を使用し、
GNU gccではsel_get_any_uid()
を使用します。
perform:
とperformSelector:
は何をするのですかperform:
とperformSelector:
は、指定されたセレクタに
対応するメッセージをオブジェクトに送信します。すなわち、
[obj performSelector:@selector(message)]
と記述するのは、
[obj message]
と記述するのと同じことです。
両者の違いは、perform:
とperformSelector:
は、セレクタを
参照する変数を引数として渡すことができるのに対し、普通に[obj message]
と
記述した場合には、常に同じメッセージを送信します。
IMP
とは何ですか。どのように取得するのですかIMP
は、メソッドの実装を表わすCのデータ型で、実装ポインタとも
呼ばれています。IMP
は、返却値がid型で、self
と
メソッドセレクタ(メソッド定義の中で変数_cmdとして参照可能)を引数の最初に
とる関数へのポインタです。
id (*IMP)(id, SEL, ...);
NSObjectの場合、次のようにして、指定されたメソッドのIMP
を
取得できます。
IMP imp=[obj methodForSelector:@selector(message)];
Objectの場合は、次のようにします。
IMP imp=[obj methodFor:@selector(message)];
IMP
で与えられたメッセージはどのように送信するのですかCの関数ポインタのように、間接参照(Dereference)します。
id anObject, theResult; IMP someImp; SEL aSelector; // ... theResult=someImp(anObject,aSelector);
IMP
をid
型以外の値を返すメソッドに使えますかIMP
はid型の値を返すメソッドへのポインタです。IMP
を
id型以外の値を返すSEL
とともに使用すると問題を起すことがあるので、
IMP
の代わりに新しい型を用意して、その型にキャストしたほうがよい
でしょう。
int (*foo)(id,SEL); int result; foo=(int(*)(id,SEL))[anArray methodForSelector:@selector(count)]; result=foo(anArray,@selector(count));
Objective-Cのランタイム関数を使用してIMP
を取得するには、
GNUのランタイムの場合、objc_msg_lookup(id,SEL)
を使用します(これを
説明してくれたJustin Hibbitsに感謝します)。NeXT/Appleのランタイムの場合は、
class_getInstanceMethod(Class,SEL)
を使用することができるでしょう。
この関数でstruct objc_method
へのポインタを取得し、
その構造体のmethod_imp
メンバが、あなたが必要とするIMP
です。
SEL
をid
型以外の値を返すメソッドに使えますかランタイムのメッセージ送信関数を直接使用するか、前の質問の中で論じたのと
同じように、メソッドのIMP
を取得し、それをキャストすることで可能です。
Apple/NeXTランタイムにおけるメッセージ送信関数はobjc_msgSend(id,SEL,...)
です。
ただし、メソッドの返却値の型によって異なる関数が提供されており、それらを使い分ける必要が
あることに注意してください。
詳細は、/usr/include/objc/objc-runtime.h
、または、Objective-C Runtime Referenceを
参照してください。簡単な例を以下に示します。
typedef int (*IntReturn)(id,SEL); NSArray *ary=[NSArray alloc]; SEL initSel=@selector(initWithObjects:),countSel=@selector(count); // id型の値が返される ary=objc_msgSend(ary,initSel,@"Hello",@"World",nil); // int型の値が返される c=((IntReturn)objc_msgSend)(ary,countSel); NSLog(@"%@: %d",ary,c);
GNUランタイムでは、objc_msg_sendv(id,SEL,arglist_t)
を使用できる
でしょう。この関数は、任意のメッセージの送信に使用できますが、コーリングフレームを
作らなければならないので、objc_msgSend()
よりも多くのセットアップを
必要とします。以下の例は、GNUstep DOコードをベースにしています。
例によって、メモリ管理は省略しています(コーリングフレームを解放する必要があります)。
NSArray *ary=[[NSArray alloc] initWithObjects:@"Hello",@"World",nil]; int *cp; SEL countSel=@selector(count); Method_t meth=class_get_instance_method([ary class],countSel); arglist_t frame=objc_calloc(8,1); const char *rettype=objc_skip_type_qualifiers(meth->method_types); int stackargs=atoi(objc_skip_typespec(rettype)); frame->arg_ptr=stackargs?objc_calloc(stackargs,1):0; cp=(int *)objc_msg_sendv(ary,countSel,frame); NSLog(@"%@: %d",ary,*cp);
返却値が普通の型(構造化されていない、sizeof(t)
<=sizeof(id)
と
なるような型 t)の場合は、前の質問で述べたように、IMP
を取得し、適切な
返却値を持つ型にキャストするのが簡単です。
例外はプログラムの実行中に起こるイベントで、通常のプログラムの流れを 中断します。Javaのような他のオブジェクト指向言語を使用していたプログラマ なら、例外についてよく知っているでしょう。Objective-Cによるプログラミング ではよく、(アサーションのテストのような)単にプログラマのエラーを見つける ために使用されたり、通常のプログラムの流れの中でユーザにエラーを知らせる ために使用されます。
例外は、今のところ、NeXT gcc(「GNU gcc、Apple/NeXT gccと呼ぶのはなぜ
ですか」を参照)ランタイムでのみ利用することができます。例外を使用するには、
gccの-fobjc-exceptions
オプションを有効にしなければなりません。
例外をスローする可能性のあるコードは、@try
ブロックの中に記述
します。@try
の後にひとつ以上の@catch
ブロックを置き、
その中でスローされた例外を処理します。@catch
に続く@finally
ブロックには、例外が発生するしないにかかわらず実行すべきコードを記述します。
例外で使用するオブジェクトはどのようなオブジェクトでもよく、@throw
キーワード
を使ってスローします。以下に例を示します。
@try { // 例外がスローされる可能性のあるコード ... } @catch(MyExceptionClass *ex) { // カスタム例外を処理する ... } @catch(id *ex) { // 一般的な例外のハンドラ ... } @finally { // ここは例外が発生してもしなくても実行される ... }
上記のコードには、ふたつの@catch
ブロックがあります。ひとつは
具体的な型の例外をキャッチし、もうひとつは一般的な型の例外をキャッチします。
最も具体的な型の例外ハンドラを先に記述する必要があります。
例外は、goto
やreturn
によって発生するコールスタックの
変化に脆弱であるので注意してください。
できます。キャッチした例外を再スローするには、@catch
ブロックの
中で、引数なしで@throw()
と記述します。
NS_DURING
, NS_HANDLER
といったマクロを使用した
例外処理を見たことがあります。これは一体何ですか。gccにObjective-Cの例外の構文が導入される前には、Cocoaは独自の例外処理メカニズム
を提供していました。それは、Cの標準関数であるlongjmp()
とsetjmp()
を使用したマクロをベースにしていました。Objective-C言語に導入された例外処理
(@try
など)の実装は、今のところ、NS_DURING
などを
使用する例外処理とバイナリ上の互換性があります。
@synchronized
とは何ですか@synchronized
は、マルチスレッドプログラミングのための単純な
ロックをサポートするコンパイラ指令です。@synchronized
は、NeXTの
ランタイム上で、gccの-fobjc-exceptions
フラグ(前述の「例外処理」を
参照)を指定することによって利用できます。@synchronized
は、明示的に
ロックを生成、管理することなく、どのようなオブジェクトでも暗黙のうちにロック
することができます。あなたがJavaをよく知っているなら、@synchronized
は
Javaのsynchronizedと基本的に同じものであると考えてください。
@synchronized
はどのように使うのですか構文は次のようになります。
@synchronized(object) { ... }
このコードは"object"をロックします。他のスレッドが同じオブジェクトに
対する@synchronized
ブロックを実行しようとするとき、実行中の
スレッドが@synchronized
ブロックの実行を終了するまで処理を
進めません。これは次のコードと基本的に同じです。
[[object getLock] lock]; ... [[object getLock] unlock];
違いは、オブジェクト自身が上記のようなメソッドを持って、明示的にロック オブジェクトを管理する必要がないことです。
明示的にロックを生成、削除する必要がないので、コードの読み書きがしやすく
なります。また、@synchronized
はreturn文と例外を認識するので、
ブロックの途中で処理を中断しても、ロックは必ず解放されます。
@synchronized
は、暗黙のロックを検索するオーバーヘッドが大きい
ので、明示的なロックに比べて処理が遅いです。また、@synchronized
は
常に再帰的なロックを使用します。再帰的なロックはそうでないロックに比べて処理が
遅いです。さらに、条件変数やその他の高度なスレッドの概念をサポートしていない
ので、柔軟性がありません。
Apple(以前はNeXT)が使用しているgccのバージョンは、Free Software Foundationが 配布しているgccとは異なるObjective-Cの実装(特にランタイムが異なる)を提供します。 DarwinとMac OS Xは、Apple gccとともに出荷されています。CocoaはApple gccに 依存しています。他のオペレーティングシステムは、一般的に、GNUのObjective-Cを 使用しています。GNUstepは、GNUのObjective-Cを必要とします。 しかし、どちらのコンパイラのソースも利用できるので、どちらでも使用したい方を あなたが使用しているプラットフォーム用にビルドできます。 Appleは独自のgccを使用しているため、両者で利用可能な機能はいつでも異なる 可能性があります。あなたが使用しているgccのバージョンのドキュメントを調べて、 利用したい機能が実際に存在するか確かめてください。
クラスライブラリを使用していない場合は、次のようにします。
gcc -c myfile.m -lobjc
クラスライブラリを使用している場合は、そのクラスライブラリのドキュメントを 調べてみてください。たとえば、Cocoaを使用する場合は、次のようにします。
gcc -framework Cocoa -c myfile.m
gccは、Objective-CとC++を混在させることができるObjective-C++と呼ばれるものを
サポートしています。Objective-C++のコードは、拡張子.mmを付けたファイルの中に
記述します。Objective-C++では、Objective-CのオブジェクトをC++のオブジェクトの
インスタンス変数に保持でき、またその逆もできます。しかし、C++のクラスを
継承してObjective-Cのクラスを定義することはできず、その逆もできません。
Objective-CとC++の例外処理メカニズムは完全に異なり、C++のcatch
を
使用して、@throw
で発生させた例外をキャッチすることはできません。
@"Hello world!"
のようなObjective-Cの文字列リテラルを
使用する場合、その文字列オブジェクトのクラスは通常NXConstantString
になります。この文字列クラスは、コンパイル時に
gccの-fconstant-string-class
オプションを使用して変更することが
できます。Cocoaでは、文字列リテラルのクラスはNSConstantString
に
なります。もし、文字列定数クラスを別のものに変更したいなら、そのクラスには
どのようなメソッドを実装してもよいですが、インスタンス変数のレイアウトは、
メモリ上のレイアウトをコンパイラが実行ファイルに書き込むので、
N[SX]ConstantString
と同じにしなければなりません
(objc/NXConstStr.h
、または、Foundation/NSString.h
を参照)。
for
構文とはどのようなものですかNSArray *bar=...; for (id foo in bar) { [foo doSomething]; }
上記のコードは、bar
の中のすべてのオブジェクトに-doSomething
メッセージを送信します。この点において、新しいfor
構文は、よく
知られたオブジェクトの列挙と発想は同じです。
NSArray *bar=...; NSEnumerator *e=[bar objectEnumerator]; id foo; while(foo=[e nextObject]) { [foo doSomething]; }
実際には両者の実装は異なりますが、特に大きなコレクションに対しては、新しい 構文の方がより早く処理できます。また、列挙中にコレクションが変更されることも 防ぎます。
#import <Foundation/NSObject.h>
完全を期して言えば、NSProxyもルートクラスです。
オブジェクトが応答できないメッセージを受信した場合、ランタイムはエラーを
発生させる前に、そのオブジェクトに対して-forwardInvocation:
メッセージを
送信します。これを利用して、あなたのクラスで
メソッド-(void)forwardInvocation:(NSInvocation *)inv
を
オーバーライドすることにより、認識できないメッセージを他のオブジェクトに
転送することができます。詳細は、developer.apple.comの「転送」の章を参照してください。また、メソッド
-(NSMethodSignature *)methodSignatureForSelector:(SEL)selector
も
オーバーライドする必要があります。そうしないと、このメソッドがnil
を
返してforwardInvocation:
が呼び出されません。
メッセージ転送の簡単な例として、コレクションを操作するループをオブジェクトの
内部に持たせる方法、すなわち、コレクションの各要素に対してメッセージを送信する
手段をコレクションに持たせる方法を以下に示します。
まずはじめに、トランポリンクラスのCollectionTrampoline
を用意
します。このクラスは、受信したメッセージをコレクションの各要素に転送します。
以下に、このクラスのインタフェイスを示します。
@interface CollectionTrampoline : NSProxy { id theCollection; } - (id)initOnCollection:(id)aCollection; @end
以下は、オブジェクトの生成と解放メソッドです。
- (id)initOnCollection:(id)aCollection { theCollection=[aCollection retain]; return self; } - (void)dealloc { [theCollection release]; [super dealloc]; }
前述したように、メッセージを転送するには、methodSignatureForSelector:
を
オーバーライドする必要があります。メソッドシグニチャを生成するAPIはないので、
ここではコレクションの最初のオブジェクトからメソッドシグニシャを探します。
もしみつからなければ、デフォルトのメソッドシグニチャを提供します。
これはあまり問題になりません。なぜなら、まだシグニチャを取得していないなら、
コレクションは空で転送が起らないか、最初のオブジェクトがメソッドを実装して
いないので、何か処理する前に例外が発生するからです。この例を提供してくれた
Michael Ashに感謝します。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSMethodSignature *sig = nil; if([theCollection count]) sig = [[theCollection objectAtIndex:0] methodSignatureForSelector:aSelector]; if(!sig) sig = [NSObject instanceMethodSignatureForSelector:@selector(release)]; return sig; }
次はforwardInvocation:
の実装です。NSEnumerator
を
使用してコレクションのオブジェクトをイテレートし、各オブジェクトのメソッドを
起動します。この簡単な例では、オブジェクトからの返却値は考慮しません。完全な
例をあげるとすれば、応答のコレクションを生成して、それを戻すことになるでしょう。
- (void)forwardInvocation:(NSInvocation *)anInv { id i; NSEnumerator *en=[theCollection objectEnumerator]; while(i=[en nextObject]) { [anInv invokeWithTarget:i]; } }
最後に、コレクションクラスがトランポリンオブジェクトを返却する方法を用意
します。これを行うNSArray
のカテゴリを以下に示します。
@interface NSArray (Trampolines) - (id)do; @end @implementation NSArray (Trampolines) - (id)do { return [[[CollectionTrampoline alloc] initOnCollection:self] autorelease]; } @end
これで、このトランポリンを使用して、コレクション中のすべてのオブジェクトに メッセージを送信することができます。
[[anArray do] doSomething];
これは、CocoaとGNUstepで分散オブジェクトまたはDOと呼ばれるものです。 DOを使用すると、異なるプロセスや同じプロセス中の異なるスレッドのオブジェクトに 対してメッセージを送信できるだけでなく、異なるシステム上で動作するプロセスの オブジェクトに対してもメッセージを送信することができます。 サーバアプリケーションは、ネームサービスにオブジェクトを登録することによって、 オブジェクトをベンド(vend)します。クライアントアプリケーションは、ネームサービス に問い合わせ、ベンドされたオブジェクトのプロキシを取得し、これを介してベンド されたオブジェクトに接続します。DOは透過的です。プロキシに送信されたメッセージは リモートオブジェクトに送信され、その結果がプロキシに返されるので、メッセージを 送信した側からみれば、ローカルオブジェクトがメソッドを実行しているかのように みえます。DOの簡単な使い方の例をコメント付きで以下に示します。 詳細は、 Apple DO guide、または、 GNUstep DO tutorial を参照してください。この例では、networked DOは使用していません。また、エラー 処理も省いています。
DOprotocol.h
このプロトコルは、サーバとクライアントの双方にインクルードされます。 ここでは、ベンドされたオブジェクトが応答できるメッセージを記述しています。 この例では、メッセージをひとつだけ記述しています。
@protocol DOExampleProtocol -(NSNumber *)myPid; @end
DOserver.m
サーバプロセスです。
#import <Cocoa/Cocoa.h> #import "DOprotocol.h" #import <sys/types.h> #import <unistd.h> @interface VendedObject : NSObject <DOExampleProtocol>{ } -(NSNumber *)myPid; @end @implementation VendedObject -(NSNumber *)myPid { NSLog(@"Vending out my pid..."); return [NSNumber numberWithInt:getpid()]; } @end int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; VendedObject *vo=[[VendedObject alloc] init]; // デフォルトのDOコネクションを取得する NSConnection *conn=[NSConnection defaultConnection]; BOOL status; // ベンドするオブジェクトを設定し、ネームサーバに登録する [conn setRootObject:vo]; status=[conn registerName:@"VendingServer"]; if(status==NO) { NSLog(@"Couldn't register as VendingServer"); exit(EXIT_FAILURE); } // コネクションを待つ [[NSRunLoop currentRunLoop] run]; [pool release]; return 0; }
このクライアントは、適切な名前を持つDOサーバをローカルマシンのみから 探します。そして、リモートプロセスのpidを取得します。
#import <Foundation/Foundation.h> #import <stdlib.h> #import "DOprotocol.h" int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; // コンパイラの型チェックのためにプロトコルを指定する id <DOExampleProtocol> clientObject; NSNumber *serverPid; /* 適切な名前を持つサーバを探し、そのサーバからプロキシを取得する。 * ホストにnilを指定するとlocalhostのみから探す。 */ clientObject=(id <DOExampleProtocol>)[NSConnection rootProxyForConnectionWithRegisteredName:@"VendingServer" host:nil]; /* -setProtocolForProxy:は、DOメカニズムに対して、リモートオブジェクトが * 指定されたプロトコルに適合していることを伝える。これにより、プロトコルで * 宣言されているメッセージは、送信する前にチェックされない。 * プロトコルで宣言されていない他のメッセージはこれまで通り送信される。 */ [clientObject setProtocolForProxy:@protocol(DOExampleProtocol)]; if(clientObject==nil) { NSLog(@"Error: did not get a proxy object for VendingServer service"); exit(EXIT_FAILURE); } serverPid=[clientObject myPid]; if(serverPid!=nil) { NSLog(@"Remote server on pid %@",serverPid); } else { NSLog(@"Error, did not get the server's pid"); exit(EXIT_FAILURE); } [pool release]; return 0; }
今のところ、gccのObjective-Cには自動ガベージコレクションはありませんが、
リファレンスカウンタ方式を使用すれば、手動でメモリ管理を行うよりは作業量は
少なくなります。NSObjectから派生したオブジェクトは、1から始まるリファレンス
カウンタをはじめから備えています。オブジェクトを参照している間に解放され
ないようにするには、そのオブジェクトに-retain
メッセージを送信
します。不要になったらそのオブジェクトに-release
メッセージを
送信します。リファレンスカウンタが0になるまでオブジェクトは解放されません。
リファレンスカウンタ方式に関するすぐれた解説が Hold Me, Use Me, Free Me にあります。
Cocoaは、キー値コーディング(KVC)とキー値監視(KVO)という、オブジェクトの プロパティの検査と変更を間接的に行う方法を用意しています。 KVCとKVOは主に、Cocoaバインディングの中で、アプリケーションのユーザインタフェイスと データモデルの間の同期を効率的に行う方法として使用されています。
オブジェクトmyObject
に-someVariable
, -setSomeVariable
の
ようなアクセサメソッドか、someVariable
, _someVariable
と
いったid
型のインスタンス変数がある場合、次のようなコード
でsomeVariable
の値に(継承されたプロパティであっても)アクセスできます。
[myObject valueForKeyPath:@"someVariable"]; [myObject setValue:@"Hello" forKeyPath:@"someVariable"];
他のオブジェクト、たとえばmyOtherObject
は、someVariable
の値が
変更されたとき、KVOによって通知を受けることができます。
[myObject addObserver:myOtherObject forKeyPath:@"someVariable" options:NSKeyValueObservingOptionNew context:NULL];
これにより、someVariable
が変更されたとき、myOtherObject
は
-observerValueForKeyPath:ofObject:change:context:
メッセージを受信します。
詳細は、 the KVO Programming Guideと the KVC programming guideを参照してください。
このFAQの中で取り上げた参考文献の他にも、以下の文献が役に立つでしょう。
The Objective-C Programming Language - Objective-Cに関するAppleの公式なドキュメント
Duncan, A.M., Objective-C Pocket Reference, O'Reilly Media (ISBN: 0-596-00423-0). GNUとNeXT/Appleの両方のObjective-Cを取り上げたリファレンス
もし、C言語(Objective-Cの基盤となる言語)でプログラミングをした経験がないなら、
次の書籍を読んでみるとよいでしょう。
Kochan, S.G., Programming in Objective-C, Sams Publishing (ISBN: 0-672-32586-1)
ただし、いくつか注意事項があります。この書籍ではまず、Objectルートクラスを
使用してObjective-Cのテクニックを紹介し、次に、第2章でFoundationについて
論じるときには、NSObjectクラスを使用します。これらのクラスでは、
いくつかのセレクタの名前(たとえば、protocol conformance)が
異なるので、読者は混乱するかもしれません。
また、GNUstepを使用するときには、ビルド時のディレクトリ構造がデフォルトで
「フラット(flattened)」であるので、313ページに掲載されているシェルへの入力の
1行目にある"cd shared_obj/ix86/mingw32/gnu-gnu-gnu"を
"cd shared_obj"に変更する必要があります。
公式FAQの c.l.o-c FAQ では、参考文献として、オブジェクト指向プログラミングに関する書籍を何冊か 紹介していますが、そこで紹介されている文献の多くが絶版になっているので 注意してください。 そしてもちろん、comp.lang.objective-cに 質問することもできますし、Googleグループで 記事のアーカイブを調べることもできます。 Appleは、 ObjC-language メーリングリストを主催しています。