こんにちは。開発担当の金内です。
Mac / iOS アプリ開発で使う Objective-C におけるルートクラスと言えば、泣く子も黙る NSObject です。Java における java.lang.Object や Python における object にあたるものが、Objective-C では NSObject なわけです。
さて、この NSObject にはクラスとプロトコルの両方があることをご存じでしょうか?そして、NSObject クラスは NSObject プロトコルを採用しています。これらはなぜ分かれているのでしょうか?クラスだけでは困るのでしょうか?
分かれている真の理由は、泣く子も黙る NSObject を設計した人の深い深い考えがあってのことでしょう。私のような凡人が知るすべはないかもしれません。
しかし、NSObject プロトコルのありがたさを感じることはできます。そのためのサンプルを書いてみました。
#import <Foundation/Foundation.h>
// 独自定義のプロトコル
@protocol MyProtocol
- (void)doSomething;
@end
// MyProtocol を採用したクラス
@interface MyClass : NSObject <MyProtocol> {
}
@end
// MyClass の実装
@implementation MyClass
- (void)doSomething
{
// ... but do nothing.
}
@end
// main 関数。ここが実行される。
int main (int argc, const char * argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
id<MyProtocol> obj = [[MyClass alloc] init];
[obj doSomething];
[obj release];
[pool drain];
return 0;
}
独自プロトコルを定義して、そのプロトコルを採用したクラスのオブジェクトを id<MyProtocol> として参照し、利用しています。
何の変哲もない(機能もない)このコードをコンパイルしてみると、次のような警告が出ます。
warning: instance method '-release' not found (return type defaults to 'id') [3]
[obj release];
^~~~~~~~~~~~~
「obj には release なんてメソッドなさそうだけど?」とのことです。
たしかに、MyProtocol は doSomething メソッドのみを宣言していて、release については触れていません。基本的にオブジェクトはすべて NSObject(のサブクラス)なのですから、当然、release メソッドはあるはずなのですが…
警告が出ないようにするためには、MyProtocol に release の宣言を追加しなければならないのでしょうか?同じように autorelease なども必要ということでしょうか?
そこで登場するのが、あの「NSObject プロトコル」です。NSObject プロトコルのリファレンスを見ると、問題となっている release や autorelease はもちろん、description や isEqual: といったメソッドが宣言されているのがわかります。
渡りに舟とはこのことですね。さっそく、MyProtocol を NSObject を継承したプロトコルにしてみましょう。
@protocol MyProtocol <NSObject> // NSObject を継承するように変更
これで id<MyProtocol> で参照したオブジェクトが release メソッドを持つことがわかって、コンパイルは何の警告もなく成功します。つまり、NSObject プロトコルを使えば独自プロトコルが他の基本的なメソッドも持っていることを簡単に宣言できるわけです。NSObject クラスしかなかったらこうはいきません。
いかがでしょうか? NSObject プロトコルのありがたさを感じていただけたでしょうか?
NSObject プロトコルのリファレンスには「If an object conforms to this protocol, it can be considered a first-class object(このプロトコルに適合していれば、第一級のオブジェクトと見なせる)」と書いてあります。つまり、NSObject プロトコルこそ、すべてのオブジェクトに共通するメソッドを宣言しているのです。NSObject プロトコルすごい!!
ふだんはあまり意識することのない NSObject プロトコル、実は縁の下の力持ちとしてフレームワークを支えてくれているわけです。
P.S. あまり触れる機会はないかもしれませんが、唯一 NSObject クラスを継承しない NSProxy というクラスがあります。これも NSObject プロトコルを採用しています。こういったクラスが存在できるのも、NSObject プロトコルのおかげですね。


















コメント
wirehead2011年09月22日 15:53
id型はNSObject *型ではないので、id型のインスタンスであるobjのdoSomethingを呼び出した際には問題がなく、releaseのタイミングで警告されたと思われます。
ただ、id型を使う際、MyProtocolはいつもNSObjectプロトコルを継承しなければいけないことに少々違和感を覚えました。クラス定義でNSObjectを継承することは常ですが、プロトコル定義でNSObjectを継承する例を見かけたことが(ほぼ)無いからです。
id obj = [[MyClass alloc] init];
ではなく、
静的型定義をするなら
MyClass *obj = [[MyClass alloc] init];
動的に行うのであれば、
NSObject *obj = [[MyClass alloc] init];
としたほうが自然だと思えますが如何でしょうか?
wirehead2011年09月22日 16:16
プロトコルを明示する山カッコがHTMLタグと認識されるようで、コメント内のそれがまとめて見えなくなっています。面倒なので自分のブログに同じ内容を貼りましたので、関心があれば参照してください。
http://d.hatena.ne.jp/wirehead/20110922/
金内2011年09月22日 16:20
wirehead様
金内です。コメントありがとうございます。
id<MyProtocol> のような参照を使いたくなるのは、クラス定義のインスタンス変数や
プロパティアクセサの引数の型などで、「id 型よりは制約を強くしたいけど、どのクラスかは特定できない」場合などが考えられます。
これを NSObject * の参照にすると、記事中の例の場合、doSomething の呼び出しで警告が表示されるので、あくまで場合によりますが、プロトコル定義で NSObject プロトコルを継承するのが妥当なケースもあるかと思います。
もちろん、NSObject のメソッドを使う必要がないケースも多いので、その場合は NSObject を継承する必要はありませんね。
wirehead2011年09月22日 16:51
何度もすみません。一番初めのコメントで山カッコとその中身が表示されなかったため、上手く伝わらなかったですね。下から2行目は次のものが正しいです。当該部分を全角記号に置き換えています。
NSObject <MyProtocol> *obj = [[MyClass alloc] init];
こうすれば、NSObject またはそのサブクラスで、MyProtocolに準拠したインスタンスを表現できると思います。警告も出ないはずです。
金内2011年09月22日 17:34
wirehead様
> NSObject <MyProtocol> *obj = [[MyClass alloc] init];
なるほど。そういうことでしたか!
NSObject<MyProtocol> * とすれば、NSObject クラスにあって NSObject プロトコルにはないメソッドも使えて便利ですね。
たいへん参考になるコメントをありがとうございます!