scrollView.canCancelContentTouches = NO; [scrollView setCanCancelContentTouches:NO];って書き方があってこれみんなどういう使い分けしてるんだろうなぁ。。って思ってます。
セッター - poohtarouの日記
セッターというか、ドット記法の話かな。
ドット記法と普通のメソッドの使い分け
まぁ、まずはこんなクラスがあったとします。
@interface Book : NSObject { NSObject *title; } @property (retain) NSString *title; @end
このクラスのオブジェクトを作ってtitleを設定/参照するコードはこんな感じです。
Book *book = [[Book alloc] init]; // ドット記法 book.title = @"Dynamic Objective-C"; NSLog(@"%@", book.title); // メソッド [book setTitle:@"詳解 Objective-C 2.0"]; NSLog(@"%@", [book title]);
これは上と下どっちのコードも同義。ドット記法でアクセスすると、setTitle:メソッドやtitleメソッドが呼ばれる。getter/setterを自動生成させないで自分で実装した場合もちゃんとそのメソッドを呼んでくれる。じゃあこんな場合はどうか。
id book = [[Book alloc] init]; book.title = @"Dynamic Objective-C"; NSLog(@"%@", book.title);
これはコンパイルエラーになる。id型のオブジェクトのメンバにドット記法でアクセスしようとすると怒られる。いやid型じゃなくてちゃんとクラスを明示して変数宣言すればいいじゃんて?NSArrayのobjectAtIndex:やNSDictionaryのobjectForKey:は返り値の型がid型ですよね。キャストすればいい?だって中に入ってるのがBookクラスのオブジェクトかどうかわかんないじゃん。isKindOfClass:で調べてからキャストする?いやいや、そのArrayの中にはGameクラスのオブジェクトも一緒に入ってて、GameクラスもsetTitleできるからそこは区別なく扱えた方が便利なんだよ!…みたいなこともあるわけで。そんなときはこう書くかなぁ。
id obj = [array objectAtIndex:0]; // Bookのオブジェクトが入ってると期待できるとする if ([obj respondToSelector:@selector(setTitle:)]) { [obj performSelector:@selector(setTitle:) withObject:@"化物語 上"]; }
はいできた。これでBookクラスのオブジェクトをid型の変数で受けてもちゃんとsetTitle:できました。ついでにsetTitle:メソッドを実装してるオブジェクトであれば、Bookクラスと継承関係になくてもsetTitle以外は全然無関係のインターフェースを実装してようとも同じように扱えます。アヒルのように鳴くものはアヒルではなく隣のおばあちゃんでした。じゃなかった、アヒルのように鳴くものはアヒルです。ちなみに、上のコードは実は
id obj = [array objectAtIndex:0]; if ([obj respondToSelector:@selector(setTitle:)]) { [obj setTitle:@"本当は怖いグリム童話"]; }
って書いてもコンパイル通るし実行時にエラーも出ない。まぁ警告出るけどね。んで、こういう場合はプロトコルを定義してあれば(そしてobjがそのプロトコルに適合してれば)警告が出ない。
// こんなプロトコルがあるとする @protocol Title <NSObject> - (NSString *)title; - (void)setTitle; @end ... id <Title> obj; ... [obj setTitle:@"本当は怖いグリム童話"];
簡単にまとめると、明示的にクラス名を指定して変数を宣言してるときはドット記法で、id型で受けるべき時はメソッドでっていうのが一応の使い分けかなー。元記事の例の場合はscrollViewはUIScrollView型で変数宣言してて、そのコンテキストではUIScrollViewであることがはっきりしてるのでドット記法でいいと思う。具体的なクラスを想定しているわけではなくて、あるアクセサを持っている何かのオブジェクトっていう扱いをするときは、respondToSelector:とperformSelector:を使うか、プロトコルを定義しといてメソッド呼び出しするか、って感じになると思う。
Key Value Conding
ところで、ObjCにはドット記法とメソッド呼び出しの他にもう一つ、オブジェクトのメンバにアクセスする方法がある。しかもこれはプロパティとドット記法が導入されたObjective-C 2.0になる前からある。例えばさっきのBookクラスのオブジェクトに対してだったらこんなことができる。
[book setValue:@"イチャイチャパラダイス" forKey:@"title"]; NSLog(@"%@", [book valueForKey:@"title"]);
恐ろしいですね。まるでオブジェクトがDictionaryか何かのよう。これはNSObjectの子孫にあたる全てのクラスで使える。そしてこれの恐しいのは、アクセサメソッドやプロパティが宣言されてなくても使えてしまうということ。例えばvalueForKey:@"title"だったら、
- titleメソッドが定義されてたらそれを呼ぶ
- titleメソッドが無くて、getTitleメソッドが定義されてたらそれを呼ぶ
- どっちも無くて、インスタンス変数titleが宣言されてたらそれを返す
- どれも無くて、インスタンス変数_titleが宣言されてたらそれを返す
っていう順番で解決されるので、Bookクラスの場合titleをプロパティとして宣言してる、つまりtitleメソッドが定義されてるのでそれが呼ばれる。もしプロパティとして宣言してなくても、title変数があるので3番目でひっかかってそれが返る。ちなみにインスタンス変数が@privateで宣言されてても関係ない。便利なんだけど怖い。1、2はともかく3、4は大分アレげなので、内部でしか使わない意図せず変更して欲しくないインスタンス変数は@privateで宣言した上でname_とかして上のルールにひっかからないようにしておく癖を付けたほうがいいと思う。外部からアクセスしてもいいやつでも名前は変えた上で明示的にプロパティで宣言した方が安全かなー。
元記事の話とは直接関係無いけど余談でした。なんでそんな仕組みがあるのってあたりは気になる人は「Key Value Coding」とかで調べてみるといいと思う。これはこれで便利だし、AppKitとかUIKitとかでは結構使われてるので。
追記
そうだブコメで指摘をもらってたんで追記しようと思ってたのすっかり忘れてた。iPhoneアプリでKey-Value Codingを一番使うであろう場面はCoreDataのNSManagedObjectですね。CoreDataでエンティティを定義してデータをつっこんだり取得したりするときは、NSManagedObjectかそれを継承したクラスがモデルオブジェクトになる。サブクラスを作るときはプロパティを定義しとけばいいんだけど、NSManagedObjectにはもちろん中のデータへのアクセサは定義されてない。じゃあどうするかと言うと、valueForKey:とsetValue:forKey:で値の取得や変更をするってことになる。
あと、これの次の記事で書いたけど、Key-Value Observingって仕組みがとても便利なので、プロパティについて調べたらついでにKey-Value CodingとKey-Value Observingについて調べておくといいと思います。まる。