さくっと基本を振り返る4
ポリモーフィズム(多態性)について考えてみる。
Java で言えばインターフェース実装、もしくは、メソッドオーバライド、C++ で言えば仮想関数とか。
まぁ早い話が大本の枠を作って、実装は個々のクラス次第ということ。
public interface IWorker { void Work(); } public class Programmer : IWorker { public void Work() { /* コード書いたり */ } } public class Teacher : IWorker { public void Work() { /* 他人に何か教えるとか */ } }
とまぁこんな感じにしといて、どっちも「Worker」だから、
Worker worker = getRandomJob(); worker.Work();
何の職業かは知らなくても、働けって言えば働いてくれる。どう働くかは実装次第という仕様。
Objective-C では、インターフェースは必要ない。だってみんな「Object」であって、メッセージを受けて動くか動かないかだけだから。
@interface Programmer { } - (void)work; @end @interface Teacher { } - (void)work; @end
と、とにかく同じメソッドさえあるのなら
id worker = [self randomJob]; [worker work];
と呼べる。
前に述べた通り、型安全などないので、randomJob の返り値が、work メッセージレセプタ(便宜上メソッドを分けて呼称)を実装しているかどうかは問題ではないし、null(nil) であっても特に問題はない。*1
とはいえ、このような書き方で、実装してるかどうか相手任せで、実行時例外を出されても困る。そんなとき、3種類の手段がある。
- protocol を作成、実装する。
- 非形式プロトコルを作成する。
- forwardInvocation で処理してしまう。
最初の方法は、Java/C++/C# のインターフェースと何ら変わりがない。なぜこのようなものがあるかというと、シリアライズされた際の転送速度を稼ぐとか、アクセス速度を稼ぐ狙いがある。
書くのが面倒だったり、必ず実装とかだるいので滅多に書かない。書くとするとこんな感じ。
@protocol Worker - (void)work; @end @interface Programmer : NSObject <Worker> { } - (void)work; @end
次に非形式プロトコルだが、これはもっと単純な実装になる。
何の事はない、共通の基底クラスにカテゴリを使ってメソッドを作ってしまう。これは、実装を変更するかどうかを、継承先で任意にできる。
@interface NSObject(Worker) - (void)work; @end
NSObject を継承するすべてのオブジェクトは、work メソッドがある事が保証される。
最後は、invocationForwarding を使う方法だ。ただし、この方法は Cocoa ありきか、、、。
@interface Neet : NSObject { id other; } @end @implementation Neet - (NSMethodSignaure*)methodSignatureForSelector:(SEL)selctor { // other がメソッド持ってたら丸投げする。 if ([other respondsToSelector:selector]) { return [other methodSignatureForSelector:selector]; } return [super methodSignatureForSelector:selector]; } - (void)forwardInvocation:(NSInvocation*)invocation { // 問答無用で other に丸投げ。テラヒドス [invocation invokeWithTarget:other]; }
という感じ。
*1:実装されていなければ例外が出るけど
さくっと基本を振り返る3
継承についても考えてみる。
またも Wikipedia 先生
あるオブジェクトが他のオブジェクトの特性を引き継ぐ場合、両者の間に「継承関係」があると言われる。
とのこと。
メソッドの動作や変数やパラメータを受け継ぐわけですな、具象化の話まで行くとポリモーフィズムな話になるので割愛と。
Java でも C# でも Objective-C にもあるにはあるな。
class Fluit { protected float sugarContent; public getSugarContent() { return this.sugarContent; } public setSugarContent(float contentvalue) { this.sugarContent = contentvalue; } } class Banana extends Fluit { /* 略 */ }
糖度というパラメータをフルーツに持たせて、それを継承しているバナナにも、糖度というパラメータがある。
継承は Objective-C では以下の書き方になる。
// Fluits.h #import <Cocoa/Cocoa.h> @interface Fluits : NSObject { float sugarContent; } @property float sugarContent; @end // Fluits.m #import "Fluits.h" @implementation Fluits @synthesize sugarContent; @end
// Banana.h #import <Cocoa/Cocoa.h> #import "Fluits.h" @interface Banana : Fluits { } /* メソッドとか */ @end // Banana.m #import "Banana.h" @implementation Banana /* メソッドとか */ @end
やっとる事はかわりません。書き方の問題で、食いやすい食いにくいかの問題。
多態性を持つための継承は Objective-C だとほとんど不要なので、ここでの継承は文字通り属性の引き継ぎ位の意味しか持たない。
加えて言えば機能の拡張も Objective-C では継承不要だ(属性の拡張はできないので注意)。これには、言語仕様のカテゴリというものを利用する。
例えば、乱暴な例ではあるが、Fluits クラスを拡張してみる。Fluits の糖度が 15 以上なら食べごろだと判定するメソッドを拡張する。
// FluitsExtention.h #import <Cocoa/Cocoa.h> #import "Fluits.h" @interface Fluits <GoodForEat> - (BOOL) isGoodForEat; @end // FluitsExtention.m #import "FluitsExtention.h" @implementation Fluits <GoodForEat> - (BOOL) isGoodForEat { return sugarContent > 15.0f; } @end // 利用例 id fluits = [Banana alloc]; BOOL isgood = [fluits isGoodForEat];
素敵。
既に継承されていようが、コンパイル済みで、ライブラリになってるオブジェクトだろうが関係なしに拡張できる。
C# 3.0 では、拡張メソッドという機能でこれが実現できる。
public static class Fluits { public static bool isGoodForEat(this Fluits target) { return target.sugarContent > 15.0f; } } // 利用例 Banana banana = new Banana(); bool isgood = banana.isGoodForEat();
C# の場合、オブジェクトの protected/private なフィールドにはアクセスできない。あくまで補助メソッドの追加程度だ(string 拡張に CSV パース機能入れるとか、メソッドの外でもできる事)。
Java 1.6 までの段階では確かできない筈だ(通常のアプローチでは)。最も、Java の場合は、中間コードに落ちるので、その仕様を知っていれば class ファイルに介入してできなくは無さそう。カバレッジツールの一部でそんな事をやってた記憶がある。
僕はその辺は知らないので JavaHacker の方何方かよろしく!
さくっと基本を振り返る
言語仕様からいえば、ポリモーフィズムに継承やインプリメントが要らないのは楽でよい。
というかメッセージという考え方の時点で既に Java のそれと全然違うのは明白。
だれだ Java のオブジェクト指向で「メッセージ」とかぬかした奴(なんかかなりいた気がするぞw)。
Hello クラスで見るオブジェクト指向の違い
Java 版。
class Hello { public string hello(String name) { return "Hello " + name + "."; } public static void main(String args[]) { Hello hello = new Hello(); System.out.println(hello.hello("Azalea")); } }
// hello.h #import <Cocoa/Cocoa.h> @interface Hello : NSObject { } -(NSString*)hello:(NSString*)name; @end
#import <Foundation/Foundation.h> #import "Hello.h" @implementation Hello -(NSString*)hello:(NSString*)name { return [NSString stringWithFormat:@"Hello %@.",name ]; } @end int main (int argc, const char * argv[]) { Hello* hello = [Hello alloc]; NSLog([hello hello:@"Azalea"]); return 0; }
へなへなっと。
実行結果で見れば一緒。でも意味合いがだいぶ違う。
Objective-C はこれができる。
int main (int argc, const char * argv[]) { id* hello = [Hello alloc]; NSLog([hello hello:@"Azalea"]); [hello release]; return 0; }
これを java で安易に書くとこう。
public static void main(String args[]) { object hello = new Hello(); System.out.println(hello.hello("Azalea")); }
当たり前だけどコンパイルエラーになる。
Java で正確に書くならこんな感じか?
import java.lang.reflect.*; Method method = hello.getClass().getMethod("hello", new Class[] {String.class}); method.Invoke(hello, new String[] {"Azalea"});
返値忘れてるが多分こんな感じ。
オブジェクト内から一致するメソッド情報抜き出して実行する。
Objective-C は実質これをやってるに等しいと思う。
故に、すべてのオブジェクトが object(id) で扱われ、ポリモーフィズムとか「そんなの関係ねぇ!」と動き回る。
実際の実装はどうこう別にせよ、「メッセージ」ってのはメソッドコールとは別のもので、オブジェクトに対する問いかけである。
あくまでただの問いかけだから、問いかける相手が誰だろうと知った事ではない。*1
*1:そして関係ないのに問いかけると「んな事知るかヴォケ!」と切れて例外を出すと、、、
さくっと基本を振り返る2
よく C++/Java/C# で使うカプセル化を考えてみる。
カプセル化の概念ちゃーなんぞやと説明面倒なので wiki 先生に聞いてみると、
データを隠蔽したり(データ隠蔽)、オブジェクトの振る舞いを隠蔽したり、オブジェクトの実際の型を隠蔽したりする
- データ隠蔽:ローカル変数とか内部実装を利用者に意識させない、セキュリティ的にローカル変数参照させない。
- オブジェクトの振る舞い隠蔽:内部実装知らせない。使う側に意識させない。
- 型の隠蔽:抽象データ型?
型の隠蔽って何だ?わからんとは俺もまだまだだな、、、、
自分自身を新しい型として宣言する(抽象データ型)って事かね?
型を定義する言語だからこの辺が正解だろうか?
後で出典確認しないとな。
で、Objective-C に話を戻すとそんなものぁない。
メソッドはすべて public*1で変数は protect である。以上。
private メソッドは結局手続きを隠して使える手段を制限することで煩雑化を抑える。が、、、、
メッセージレセプタはあくまで「振る舞い」だから隠蔽するものでもなく、、、、
メッセージ送信は前回触れたように型とか既に意味がない。
結局オブジェクトというものを作って、メッセージが送付できる。
メッセージをどう解釈してどう動くかはオブジェクト次第(しかもそのオブジェクトは実行時に決まる)。
*1:アンドキュメントメソッドも実質 public に呼べる
OS3.0 の公式 UnitTest 試してみた
さくっとテスト対象プログラム
@interface HelloObject : NSObject { } - (NSString*)helloMessage:(NSString*)name; @end
#import "HelloObject.h" @implementation HelloObject - (NSString*)helloMessage:(NSString*)name { return [NSString stringWithFormat:@"Hello %@.",name]; } @end
この後、ターゲット追加で、UnitTest を追加。
名前は LogicTests でいーや
こいつをアクティブターゲットに設定する。
Tests グループ作ってテストコード隔離する。
Objective-C test case class を選択して作成。これも HelloTests でいーや。
ここで HelloTest をターゲットに指定する。
とゆーか普通のプロジェクトにこんなもん組み込んでもしょうがねえ。
今回はアプリケーションテストしないのでさくっとテスト書く。
@interface HelloTests : SenTestCase { } - (void)testHello; @end
テストの本体
#import "HelloTests.h" #import "HelloObject.h" @implementation HelloTests - (void) setUp { // Set up } - (void) tearDown { // Tear down } - (void)testHello { HelloObject* hello = [HelloObject alloc]; [hello autorelease]; STAssertTrue( [[hello helloMessage:@"Azalea"] isEqualToString:@"Hello Azalea."], @"Hello test is failed." ); } @end
これだけではまだテストできない。
まぁテスト対象のコードをバンドルに突っ込むだけだけど。
とりあえずこれでロジックテストは実行できる。
ただし「Simulator」の「OS 3.0 以上」でのみ。
クラス図を書こうとしてみた
普通のクラス図エディタだと、どうも Objective-C のメソッドを表現できないので、XMind で書いてみた。
うん良さげ。
iPhone から AppStore の検索URLにリンクする
通常、 Skype とかのダウンロードサイトを開くときは、以下の手順を踏む。
- PC で iTunes 起動して、商品を検索(ここでは skype)
- 商品名を右クリックして、「iTunes store URL をコピー」を選択。
- 「http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304878510&mt=8」こんな感じでコピーされる。
- 「iTunes」を「phobos」に置き換える。(置き換えない場合、URLを開くと、一旦 Safari を起動したあとで、AppStore が起動する)
で、最後に次のコードを書く。
NSString* urlString = @"http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304878510&mt=8";
NSURL *skypeURL= [NSURL URLWithString:urlString];
[[UIApplication sharedApplication] openURL:skypeURL];
検索したいときのURLとはなんぞやと思ったら、iPhoneDevelopperJapanでGClue社の佐々木様が書いてくださってた。
NSURL *skypeURL = [NSURL URLWithString:@"http://phobos.apple.com/WebObjects/MZSearch.woa/wa/search?entity=software&media=software&submit=seeAllLockups&term=skype"];
[[UIApplication sharedApplication] openURL:skypeURL];
AppStore を検索モードで起動してくれると。
公式ドキュメントでは上半分の Tips しかなかったぜよ。
http://developer.apple.com/iphone/library/qa/qa2008/qa1629.html