Blocks の戻り値の型に注意

最近お気に入りの Blocks ですが、ハマったところがあったので、それを紹介しようと思います。

例えば、次のような Block を宣言すると型が違うと言われてエラーが出ます。

float (^zero)() = ^{
        return 0.0;
     };

NSLog( @"%f", zero() );

さて、何がいけなかったのでしょうか。実は、0.0というのが float 型だと思われてないのです。つまり、キャストするか f を付けることで float 型と思わせることで、このエラーを取り除くことができます。

float (^zero)() = ^{
        return 0.0f;
     };

NSLog( @"%f", zero() );

このように、 Block の戻り値は厳密に型チェックされるようで、暗黙型変換はしないようです。なぜか Blocks を使っていて型エラーが出てしまう時には、型を確かめるようにしましょう。

なぜ Objective-C はメッセージ式なのか

Objective-C が奇妙に見える大きな要因のひとつは、明らかにメッセージ式の存在だと思います。なぜ Objective-C はメッセージ式という奇妙な式を用いてメソッドを呼び出すようになったのでしょうか? Objective-C の作者にどういった意図があったのかは知りませんが、私なりに Objective-C がメッセージ式でなければいけない理由を見つけていますので、そのお話をしようと思います。

まず、他の多くの言語で採用されている、 . によるメンバアクセスによるメソッド呼び出しを考えてみましょう。

anObject.method( );

みなさんはこれをどう解釈するでしょうか? インスタンス anObject の method メソッドを実行する。そう解釈するのが普通だと思われます。なぜなら、 . は、多くの場合メンバへのアクセスを意味するもので、 anObject のメンバである method メソッドを呼びだすと考えることができるからです。

call method

それでは、こちらはどうでしょうか?

[anObject method];

みなさんはこれをどう解釈するでしょうか? anObject の method メソッドを実行するでしょうか?そう思った方は、まだ Objective-C に不慣れな証拠です。これは、 anObject に method メッセージを送信すると解釈すべきです。

send message

Objective-C では、オブジェクトがメッセージを受け取ること、すなわちメソッドの実行というわけではありません。現実としては、対応するメッセージの実装が呼び出されていますが、それをメソッド呼び出しと呼ぶことには、再考の余地があります。

例えば、Objective-C では、デリゲートがあります。デリゲートは、メッセージを受け取ったオブジェクトが、デリゲートオブジェクトにメッセージを転送し、そのデリゲートのメソッドに委譲して処理させるための仕組みです。この場合、誰がメソッドを実行していますか?デリゲートオブジェクトです。すなわち、必ずしもメッセージを受け取ったオブジェクトが、実際に処理するメソッドを実行しているわけではないのです。メッセージを受け取ったオブジェクトは、メッセージを転送するという処理のメソッドを実行しているのです。

例えば、 Objective-C では、オブジェクトにどんなメッセージを投げることも許容しています。 . によるメンバへのアクセスならば、メンバが無い時点でエラーを出すべきであり、コンパイル時にエラーを出します。しかし、 Objective-C の場合、たとえ実装のないメッセージでも、コンパイル時は警告のみで、コンパイルすることは可能です。これは、オブジェクトはどんなメッセージも受け取るということです。

受け取ったメッセージに対応するメソッドの実装がなかった場合、そのメッセージはどうなるでしょうか?何もしなければ例外が発生するようにできていますが、 Objective-C ではそのようなメッセージを処理する仕組みが用意されています。 methodSignatureForSelector: と forwardInvocation: を適切にオーバーライドすることによって、対応する実装のないメッセージに対して、処理をすることができます。処理の内容は、代わりに適当なメソッドを実行させるようにしたり、別なオブジェクトにメッセージを転送したり、例外を発生させたりと、さまざまな処理を記述することができます。

Objective-C では、メッセージとメソッドを分けて考えるべきだと思います。 . によるメソッド呼び出しの記述は、まさにそのオブジェクトのメソッド実行を意味しますが、オブジェクトへのメッセージ送信は、必ずしもメソッド実行と結びつかないわけです。メッセージ送信のイメージは、メソッド実行ではなく、オブジェクト同士の会話を想像すべきです。メッセージ送信先の相手が、どのようにメッセージを解釈しようと、適切に返事をしてくれさえすれば、何も気にすることはないのです。

メッセージ式に慣れてくれば、この [ ] を見ると、オブジェクトにメッセージを投げているように見えてくるはずです。そして、 . とは違う意味があることに気がつくことでしょう。

Objective-CとC言語の速度比較なんて無意味

Objective-C と C言語の速度差を比較した話がありましたが、そもそもCの完全上位互換である Objective-C と比較しても、何も意味はないと思います。例え Objective-C を使っていたとしても、速度が必要なところは C で記述するのが普通だからです。それを簡単にできるが Objective-C のいいところでもあります。

私の意見はそれだけなのですが、Objective-C と C のコードが、到底同じことをやっているようには見えなくて納得がいかなかったので、同じような動作になるようにCのコードを書きなおしてみました。

- (void)draw {
	glColor4f(0.8, 1.0, 0.76, 1.0);
    glLineWidth(4.0f);

#ifdef USE_OBJC
	for (int i = 0; i < screenWidth; i++) {
		NSNumber *tempNumber = [lineArrayX objectAtIndex:i];
		CGFloat tempFloat = [tempNumber floatValue];
		if (tempFloat > screenWidth) {
			[lineArrayX replaceObjectAtIndex:i withObject:[NSNumber numberWithFloat:0]];
		} else {
			[lineArrayX replaceObjectAtIndex:i withObject:[NSNumber numberWithFloat:(tempFloat + 2)]];
		}

		CGFloat y = 8.0f * cosf(i / 60.0f * 3.14);
		ccDrawLine( ccp(screenWidth - tempFloat, 40 + y), ccp(screenWidth - tempFloat, -screenHeight) );
	}
#else
	for (int i = 0; i < screenWidth; i++) {
		CGFloat tempFloat;
		CFNumberGetValue( CFArrayGetValueAtIndex( lineArrayX, i ), kCFNumberFloat32Type, &tempFloat );
		CGFloat zero = 0.0;
		if (tempFloat > screenWidth) {
			CFRelease( CFArrayGetValueAtIndex( lineArrayX, i ) );
			CFArraySetValueAtIndex( lineArrayX, i, CFNumberCreate( NULL, kCFNumberFloat32Type, &zero ) );
		} else {
			CGFloat temp2Float = tempFloat + 2;
			CFRelease( CFArrayGetValueAtIndex( lineArrayX, i ) );
			CFArraySetValueAtIndex( lineArrayX, i, CFNumberCreate( NULL, kCFNumberFloat32Type, &temp2Float ) );
		}

		CGFloat y = 8.0f * cosf(i / 60.0f * 3.14);
		ccDrawLine( ccp(screenWidth - tempFloat, 40 + y), ccp(screenWidth - tempFloat, -screenHeight) );
	}
#endif
}

NSAutoreleasePool を使っているか否かの差のみで、ほぼ同じようなことをしているコードになっています。

結果、iPhone 3G iOS 4.1 で Objective-C では約 27 FPS 、C言語では約 35 FPS でした。

さらに同じコードとなるように、Objective-C のコードにも手を加えると約 25 FPSとなり、約 10 FPS の差が出ました。

- (void)draw {
	glColor4f(0.8, 1.0, 0.76, 1.0);
    glLineWidth(4.0f);

#ifdef USE_OBJC
	for (int i = 0; i < screenWidth; i++) {
		NSNumber *tempNumber = [lineArrayX objectAtIndex:i];
		CGFloat tempFloat = [tempNumber floatValue];
		if (tempFloat > screenWidth) {
			[[lineArrayX objectAtIndex:i] release];
			[lineArrayX replaceObjectAtIndex:i withObject:[[NSNumber alloc] initWithFloat:0]];
		} else {
			[[lineArrayX objectAtIndex:i] release];
			[lineArrayX replaceObjectAtIndex:i withObject:[[NSNumber alloc] initWithFloat:(tempFloat + 2)]];
		}

		CGFloat y = 8.0f * cosf(i / 60.0f * 3.14);
		ccDrawLine( ccp(screenWidth - tempFloat, 40 + y), ccp(screenWidth - tempFloat, -screenHeight) );
	}
#else
	for (int i = 0; i < screenWidth; i++) {
		CGFloat tempFloat;
		CFNumberGetValue( CFArrayGetValueAtIndex( lineArrayX, i ), kCFNumberFloat32Type, &tempFloat );
		CGFloat zero = 0.0;
		if (tempFloat > screenWidth) {
			CFRelease( CFArrayGetValueAtIndex( lineArrayX, i ) );
			CFArraySetValueAtIndex( lineArrayX, i, CFNumberCreate( NULL, kCFNumberFloat32Type, &zero ) );
		} else {
			CGFloat temp2Float = tempFloat + 2;
			CFRelease( CFArrayGetValueAtIndex( lineArrayX, i ) );
			CFArraySetValueAtIndex( lineArrayX, i, CFNumberCreate( NULL, kCFNumberFloat32Type, &temp2Float ) );
		}

		CGFloat y = 8.0f * cosf(i / 60.0f * 3.14);
		ccDrawLine( ccp(screenWidth - tempFloat, 40 + y), ccp(screenWidth - tempFloat, -screenHeight) );
	}
#endif
}

NSNumber と CFNumber 実は同じなのですが、NSNumber のイニシャライザは、CFNumberCreate より多少簡易的になっているので、ラップされているものだと思われる。そういう多少の違いはあるにせよ、Objective-C のメソッド呼び出しによるオーバーヘッドはこの程度なのだと思います。

修正したファイル

iPhone 開発雑感

最近 iOS SDK で iOS (iPhone) のアプリケーションを作成しているのですが、一通りの基本的なインターフェースは構成できるようになったので、その感想でも書きます。

Mac などのデスクトップアプリケーションでの UI の構成要素は Window と View 両方が主役ですが、iOS(iPhone)では一枚の Window に View を貼りつけていく形になります。そうなる理由は簡単で、あの小さな画面には Window が一枚で十分なのです。

iOS でもっともよく用いられる View は、おそらく TableView です。iOS では、この TableView が最も基本的な View となっています。デスクトップアプリケーションの Table というと、リストを表示することが主な目的となりますが、iOS の場合はそれだけでなく、さまざまな場面で用いられています。

例えば、環境設定の画面がありますが、あれもまさに TableView です。環境設定くらいならば、まだ TableView の原型を留めていますが、カスタムの TableViewCell を用いた TableView で構成されていたりすると、一見 TableView に見えない画面も、TableView で構成できます。

iPhone は、画面が小さいので、たくさんの情報を一度に表示することはできません。したがって、画面の遷移が多くなるわけですが、iOS では NavigationView でそれを見事に解決しています。この NavigationView を用いると、とても簡単に画面を遷移させることができ、View 間の移動をとても簡潔にコーディングすることができます。

私がもっとも iOS SDK に感心を抱いたのは、このような画面遷移の仕組みが用意されているところであり、それらの要素になる TableView や通常の View 、さらに Navigation の外側の TabBar がありますが、それらの連携がとてもスムーズな点です。

ユーザー側に洗練されたインターフェースを提供するだけでなく、デベロッパ側にも洗練されたインターフェースを提供できるのは、とても素晴らしいことだと思います。

衝撃の温度グラフ

自宅サーバーの温度をモニターして24時間のグラフにしているのですが、この前面白い温度変化が見られたので、せっかくなので載せておきます。

グラフ

何が起きたのか不思議でしたが、家にいた人に聞いてみたところ、にわか雨降ったそうです。そして、サーバーのすぐ隣の窓から雨が吹き込んだらしい。

iPhone 4 を買おうと思います

ホワイトが欲しかったのですが、年内延期となると、今の 3G で年内までやっていくのは難しいのと、購入周期に影響が出るので、近々 iPhone 4 の黒いやつを買ってしまおうと思います。

ホワイトほしいなぁー。。。

雷の影響でサーバーが落ちてます

ここ2日連続、落雷の影響で停電し、サーバーの電源が落ちて、繋がらない状況が発生しております。この時期、まだまだ停電の余地がありますので、もし夕方アクセスして繋がらなかった場合、しばらく待ってからアクセスし直してください。

iPhone Developer Program に早く参加したいです

ようやく iPhone 開発するきっかけができたので、開発しつつ iPhone Developer Program に申し込んだのですが、想像よりも手続きが難航していて困っています。

日本語表記のアカウントだと問題が発生する噂は聞いていたので、英語表記のアカウントを作成したのですが、支払い手続きのところでアップルストアに飛ばされるようになっていたので、結局住所は日本語表記にせざるを得ない状況に。さらに、名前はローマ字表記のままだったので、支払い者の情報が日本語表記とローマ字表記が入り乱れる不思議な状況に。

とりあえず、銀行振込で完了し、アクティベーションコードはすぐに届いたものの、支払い者とアカウント情報が一致しないと言われる有名なエラーにひっかかります。

そして Contact us からサポートメールを送ったのですが、返事は1週間後でした。もうちょっと早いと思ってたので、ちょっと残念です。

ようやくアクティベーションが完了するかと思いきや、手動でアクティベーションする際に必要な、アップルストアの購入履歴のページがエラーで表示されず、アクティベーションできず。別のページから同じページへ飛ぶと、メンテナンス中なので後日アクセスしてください、とのことでした。

そして今日、4日経ってもメンテナンスが完了しないので、またサポートにメールを送りました。そもそも、他のアカウントでは正しく表示できるので、メンテナンスではなくて、何かエラーがおきてる気がしますが、、。

そんなこんなで、未だに実機でテストできません。もうちょっと手続きが楽になるといいのですが、どうにか改善されないものでしょうか。

Objective-C クイックリファレンスを公開します

最近 Objective-C が注目されてきているので、ここらへんでクイックリファレンス的なものを書いてみました。まだすべての項目は書いていませんが、半分くらい書けたので公開します。

これを書き終えたら、次は Interface Builder のほうを書いてみたいです。

近日中にサーバーのメンテナンスを行います

近日中にサーバーのメンテナンスを行いますので、その間は閲覧ができなくなりますので、あらかじめご了承ください。

うちの自宅サーバーは、Mini-ITXで省スペースなものを作ったのですが、よくよく考えると、自宅が広いので全くMini-ITXに合わせたケースである必要がなく、大変な誤算だったと思っています。これから夏を迎えるにあたって、小さなケースではなく、大きなケースに変更し、もう少し放熱性が高そうな雰囲気にします。ファンも、その分もう少し大きく風量を持ったものを付けることができそうなので、ケースとファンの交換をします。

と、自作をしたことがない素人のお話でした。実際これでCPUの温度が多少下がるかが気になります。