Grand Central Dispatch その2 Queue

Queueを作る

ディスパッチの基本的な使い方は、処理したいコードをブロックや関数で用意しておいて、キューにそのブロックや関数を渡すという流れになります。

ディスパッチのキューには2種類あります。アプリ(OS?)に最初から一つ用意されているグローバルキューと、自分で作るキューです。グローバルキューは並列に処理できますが、自分で作るキュー(以後シリアルキューと書いて区別します)は入れていった順番に直列でしか処理できません。

キューはdispatch_queue_tという型のオブジェクトで表されます。ディスパッチのオブジェクトは他にもいくつかありますが、それは次回以降に後回しにします。

グローバルキューを取得するにはこの関数を使います。

dispatch_queue_t
dispatch_get_global_queue(long priority, unsigned long flags);

一つ目の引数にはキューが処理される優先度を指定します。普通はDISPATCH_QUEUE_PRIORITY_DEFAULTを指定すれば良いと思います。DEFAULT以外にもHIGHとLOWがあります。HIGHの処理が全て終わってからDEFAULT、DEFAULTが全て終わってからLOWという順番で処理されていくようです。2つ目の引数はヘッダを見るとReserved for future useとなっていて今は何も意味しないので、0を入れておけば良いです。

シリアルキューはこの関数で作ります。

dispatch_queue_t
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);

一つ目の引数はキューに付けられるラベルで、サンプルを見る限り逆DNS記法が推奨されているようです。でも、同じラベルでふたつキューを作っても、同じキューが返されるという事は無く、別のキューが作られるようです。2つ目の引数はこれまたヘッダにUnusedなんて書かれていて、こちらはNULLでも入れておけば良いようです。

なお、Carbonに慣れ親しんでいる方はお分かりかと思いますが、関数名にcreateとついているのでdispatch_queue_createで作ったキューは使い終わったら解放しなければいけません。それを行うのはdispatch_releaseです。

dispatch_release(queue);

使うかどうか分かりませんが、リテインカウントをあげる事も出来ます。

dispatch_retain(queue);

これらretainとreleaseはキュー以外のオブジェクトにも使います。

Queueに処理を渡す

キューを取得できたら、ブロックを渡して処理をさせます。キューを使って処理を開始させる関数はいろいろありますが、まず基本的な2つを見てみます。こんなかんじで定義されています。

void
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
void
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

ayncは非同期でバラバラに、syncは同期で順番に、キューにブロックを処理をさせようとする関数です。と説明すると、キューにも同じような用途で2種類あるのに何で関数がまた同じように2種類分かれているんだと思ってしまうのですが、ちょっと役割が違います。

ブロックが処理されるスレッドを調べてみると、asyncは呼び出し元のスレッドとは別のスレッドでブロックを処理させ、syncは呼び出し元と同じスレッドで処理をさせるようです。

実際の挙動としては、グローバルキューにasyncでブロックを渡すと別スレッドで並列に実行されますが、syncで渡すと現在のスレッドをつかうのでそのまま直列に実行されます。シリアルキューにsyncで渡せばもちろんそのまま直列に実行ですが、asyncで渡すと入れていった順番に別スレッドで実行されます。

グローバルキュー+syncの組み合わせはありえないような気がするんですが、もしかしたら想像もつかない用途があるかもしれません。他にもディスパッチ用の関数はたくさんあるんで意味が無さそうな組み合わせはいろいろ出てくるかもしれません。

それと、ここでつかえるブロックは返り値も引数もなしのブロックです。

実行してみる

実際にディスパッチを使ってみます。ディスパッチを使うには<dispatch/dispatch.h>をincludeしないといけないのですが、Objective-Cで使う場合はFoundationに既に含まれているので何も気にしなくても使えます。

以下、サンプルのソースコードです。単純にディスパッチを試すためC言語だけで組んであります。新規プロジェクトをCommand Line ToolのTypeをCで作成して、main.cを以下のコードに差し替えてください。あっ、もちろんSnow LeopardのXcode3.2以降で実行してください。

#include <stdio.h>
#include <stdlib.h>
#include <dispatch/dispatch.h>
int main (int argc, const char * argv[]) {
    
    dispatch_queue_t gQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_async(gQueue, ^{
        
        dispatch_queue_t sQueue = dispatch_queue_create("jp.objective-audio.sample1", NULL);
        
        for (int i = 0; i < 10; i++) {
            dispatch_sync(sQueue, ^{
                printf("block %d\n", i);
            });
        }
        
        dispatch_release(sQueue);
        
        printf("finish\n");
        exit(0);
    });
    
    dispatch_main();
    
    return 0;
}

dispatch_asyncで非同期で実行するブロックの中でシリアルキューを作って、10回ほどdispatch_syncでブロックを実行させています。その処理が終わったら”finish”と表示してアプリ終了です。

ブロックの記述は、わざわざ宣言とかしなくても、こんなかんじで関数の中に直接書けちゃいます。

最後のdispatch_main()というのはメインスレッドを休止させるもので、これを使わないとdispatch_asyncでグローバルキューにブロックを実行させる前にアプリがreturn 0で終了してしまいます。NSApplicationMain()かCFRunLoopRun()が走っている場合には必要ありません、というかdispatch_main()を呼び出すと固まります。

キューの種類を入れ替えてみたり、syncとasyncを入れ替えてみたりすると、どんな感じで動作するか分かるかと思います。asyncのところをsyncにするとdispatch_main()が無くてもちゃんと全部実行されますし。逆にsyncのところをasyncにすると、ブロックの処理が別スレッドに行ってしまうので、実行される前に”finish”となります。

とりあえず今回は以上です。asyncやsync以外にもディスパッチでブロックを実行させる関数があるので、また次回見ていきます。

Grand Central Dispatch その1 Block

Grand Central Dispatchについて日本語で丁寧に詳しく説明してくれるサイトはないかなぁと待っていたのですが、待っている間にひととおり自分なりに調べ終えてしまったので、まとめて書いておきます。まちがいに気がついたらご指摘いただけるとうれしいです。

Grand Central Dispatch(以下ディスパッチと書きます。リファレンスとか関数名とかGrand Centralって全然ついていなくてDispatchだけですし。)とはなんぞやというと、Snow Leopardから導入された並列処理のAPIです。並列処理といえばLeopardではNSOperationというObjective-CのAPIが導入されましたが、ディスパッチはC言語のAPIとして用意されていて、システムに近い低レベルなところで使えるものです。

あれこれ試した印象で言うと、コアが2つしかないCPUのMacでたいした事の無い処理をなんでもかんでもディスパッチで並列にしても、ディスパッチのオーバーヘッドが大きくて逆に遅くなってしまうので、どこで使うかってのはよく考えた方が良さそうです。Mac Proみたいにコアがたくさんあると効果が大きいんだろうなぁと思います。

Blockの構文

ディスパッチでは一つ一つの処理をブロックという単位でキューに渡して処理を実行します。ブロックの代わりに関数を使う事も出来るのですが、あえてこのタイミングでC言語を拡張して用意しているくらいですので、ブロックを使うというのが正当な使い方だと思います。

ブロックは変数に入れておく事ができ、ブロック名をname1とname2とすると宣言はこうなります。

void (^name1)(void); //返り値なし、引数なし
int (^name2)(float, int); //返り値int、引数floatとint

名前の頭にキャレット(^)を付けます。で、これらへ代入をするのはこうなります。

name1 = ^{
    //ブロックの処理を記述
};
name2 = ^(float f, int i) {
    //ブロックの処理を記述
    return (int)f + i;
};

ブロック本体は、キャレット(^)、引数、その後にブレース({})で囲んで記述します。name1のように引数がvoidなら省略できます。なお、name2のように返り値があったり、引数が2つ以上のブロックはディスパッチでは使いません。

ブロックは関数と違って、メソッドや関数の中に置けます。関数のような感じで外にも置けます。

呼び出す時は関数と一緒です。

name1();
name2(3.0, 5); //返りは8

ブロック変数

ディスパッチでブロックを使うときには返り値が無いとなると、処理結果を得るためにはどうするかって事なんですが、そのひとつにブロック変数というのがあります。たとえば、以下のようなコード。

#import <Foundation/Foundation.h>
int y = 0;
int main (int argc, const char * argv[]) {
    
    int x = 0;
    
    void (^inc)(void) = ^{
        x++; //エラー
        y++;
    };
    
    inc();
    
    printf("x = %d / y = %d\n", x, y);
    
    return 0;
}

これはx++のところでエラーが出てコンパイルが通りません。

ブロック内では、グローバル変数には自由にアクセスできますが、関数内のブロック外にあるローカル変数は、そのままでは読み込めるだけで書き込みは出来ません。ディスパッチでブロックを処理するときには、呼び出した関数とは別のスレッドで処理される場合があるので(というかそれがディスパッチを使う目的)、関数が終わったら消えてしまうローカル変数には戻せないという事だと思います。

ローカル変数に書き込むには__blockという修飾子をつけます。こうすることで、その変数が使われるブロックがすべて終わるまで保持されるようです。

#import <Foundation/Foundation.h>
int y = 0;
int main (int argc, const char * argv[]) {
    
    __block int x = 0;
    
    void (^inc)(void) = ^{
        x++;
        y++;
    };
    
    inc();
    
    printf("x = %d / y = %d\n", x, y); // x = 1、y = 1
    
    return 0;
}

とりあえずブロックはこんなところで、次回は実際にディスパッチを使ってみます。

BigStopWatch v2.3 公開しました

BigStopWatchのver2.3が公開されました。

新機能は、バッテリー残量の表示です。インストール時には機能がオフになっていますので、必要な方は設定でオンにしてください。バッテリー残量表示の機能を付け加えた事で、OS 3.0のみの対応になっています。

あと、ラップ/スプリット機能・カウントダウン・積算の各機能をオン・オフ出来る設定を追加しました。これら3つ全てをオフにすると、このアプリを最初に作ろうとしていた理想の状態になったりします。もともとは積算も付けたくなかったんですが、さすがにそれはシンプルにしすぎだろうと思って、苦肉の策で小さい円の中にRESETを浮かび上がらせるということにしましたので。

ちなみに、アニメーション周りをまたちょっと変更していまして、多少パフォーマンスが落ちています。iPhone 3Gとかだとカクッとする事が多くなってしまったかもしれません。そのかわり内部的にはアニメーションを自由にいじくれる様になってるんですけど…。

日本語ドキュメントに追加

今日アップルの日本語ドキュメント見たらOS3.0の新機能のものが追加されてました。前からあった2.X用のドキュメントも、そのうち3.0用に直される模様。

iPhone Dev Centerの日本語ドキュメント
※もちろん要アカウント

追加になったのは、

・Apple Push Notificationサービス プログラミングガイド
・Store Kit プログラミングガイド
・Game Kit プログラミングガイド

の3つ。

ちょうどGame Kitでやりたい事があって調べようと思っていたからうれしいです。

同じページに日本語訳されているAudio Session プログラミングガイドは、全iPhoneアプリ開発者必読だと思いますので、ついでにぜひ。ちょこっとでも音を鳴らすなら間違いなく知っていた方が良いです。

iPhone OS 3.0のオーディオ周りの変更点

OS3.0がでてからもう結構時間が経ってますが、オーディオ周りの挙動に変更があったところをまとめておきます。

ロック時に音が途切れない

AudioUnitのRemoteIOを使っている場合、AudioSessionのPreferredHardwareIOBufferDurationを何も設定せずデフォルト状態のままだと、以前と変わらずロックしていないときは1024フレーム、ロック中は4096フレームと切り替わってしまいますが、一度値を設定しておくと、ロックしても設定した値そのままで切り替わらなくなったようです。

RemoteIOのデフォルトフォーマットが変更

OS2.2.1以前のRemoteIOのデフォルトのフォーマットは8.24の固定小数点でしたが、OS3.0からは16bitの整数に変わっているようです。ただし、MultiChannelMixerなどはあいかわらず8.24の様ですので、組み合わせて使う場合は自分で設定して合わせる必要があり、注意が必要です。

Objective-CでAudioSession

いままでのC言語でのAPIだけでなく、AVFoundationにAVAudioSessionというObjective-CのAPIも加わり、より簡単にAudioSessionが使えるようになったようです。でもなぜかIOBufferDurationが設定できないんですよねぇ。使い方が間違ってるんでしょうか。

ExtAudioFileでMP3やAACなどの圧縮ファイルが使える

AVAudioPlayerでは以前から読み込めていましたが(AudioQueueでもでしたっけ?)、OS3.0からはExtAudioFileでも読み込めるようになったようです。個人的に録音に興味が無いので試していませんが、AACとかは書き込みも出来るかも。

Bluetooth経由で音の再生が可能に

BluetoothのA2DPに対応し、ワイヤレスで音の再生が可能になりました。が、手元にあったBluetoothレシーバ(以前使っていたTOSHIBAの携帯用のやつ)で試しに聴いてみたところ、音質は別にいいのですが、レイテンシーがひどすぎて、Touch the Waveとか楽器アプリで使うのはありえないという印象です。

ミュージックライブラリにアクセスが可能に

と聞いて期待したんですが、一瞬で打ち砕かれました。あくまでメタ情報の取得と、用意されているプレイヤーでの再生が出来るだけで、オーディオファイルを直接読み込む事は出来ません。なんかよく、ミュージックライブラリに直接アクセスなんて書いてありますが、「間接的」って表現のほうが合ってると思います。あ、でも、僕の認識が間違っていて、実はオーディオファイルの読み込みができるなんて情報があればぜひとも教えていただきたいです。

OS3.0対応状況

Touch the WaveのOS3.0対応バージョンはAppStoreに提出してはいるのですが、いまだ公開されていません。他の様子も見ていると、最近はかなり審査に時間がかかっているみたいですので、しばらくおまちください。今月中には出てくれますかねぇ…。

Touch the Waveを3.0で動かした場合のどんな不具合が出るかというと、ダウンロード画面・プレイリスト画面・プレイヤー画面を行ったり来たりしていると、そのうち移動できなくなり、それでも移動しようとすると落ちてしまうという感じです。ViewController周りの実装をいいかげんにしていたツケが回ってきたというところでしょうか。

ということで、3.0に対応するだけバージョンは審査待ちですが、そんなこんなしているうちに、もうちょっといろいろ変更してるバージョンは大体できあがってます。まあ、もうちょっとじっくりと動作を検証して7月あたりに公開しようかなぁと思っています。

BigStopWatchは今のバージョンのままで大丈夫だと思います。こっちはOpenGL ESがほとんどで、問題出てきそうなCocoaTouchなAPIはほとんど使っていませんので。

3.0対応

iPhone OS 3.0公開のタイミングに合わせてTouch the Waveをいろいろといじくり回していたのですが、予想していた7月より1ヶ月も早かったので、とりあえず3.0に対応しただけバージョンをApp Storeに提出しておきました。

OS3.0に対応しただけと言いつつ新機能もあるのですが、3.0からの新機能に関わる部分で、まだ書いていいかどうかわからないので、とりあえず公開してからのお楽しみという事にしておきます。基本的にはView周りの挙動が変わって落ちたりしていたので、その修正です。

他のアプリの3.0対応の駆け込みアップデートが多かったりして、普段よりも時間がかかったりするんでしょうかねぇ。今までの感じだと16日くらいにはReady for saleになると思うんですけど。

あと、提出したアプリはOS2.2.1以前は切り捨てているのですが、17日より早くレビューが終わったらOS3.0公開前にApp Storeに並んじゃうんでしょうか。ちょっと気になります。

renew

iPhoneのDeveroper Programの更新の案内が来ていたので、さっさと更新しました。たしか60日以内にって事が書いてあったような気がしたので、期限が切れる2ヶ月前になったら順次メールが届くっていう感じなんでしょうか。

去年の7月にアクティベートしようとしたときにはAppleに電話しなくちゃいけなかったりとめんどくさかったのに比べて、さすがに更新なだけにあっさり一発でアクティベートできました。でも、アクティベーションコードが来たのが購入してから1日以上とあいかわらず遅かったですね。

ところで、最近はTouch the WaveのOS3.0対応をのんびり進めております。ただ3.0に対応するだけならちょっとした変更でよいのですが、このタイミングに合わせていろいろとやっているので、完成するのはまだまだ1ヶ月以上かかりそうです。プレイヤー画面をフルOpenGLで作り直したり、3.0の新機能を使ってみたり。3.0の正式版が出るのが早かったら間に合わないかもしれません。

あと、そんな感じなんで当分このページもあんまり更新できないと思います。

BigStopWatch v2.2 公開

予告通り、BigStopWatchのv2.2が公開されました。

主な変更点は前回のエントリーに書いた通り、
・Normal modeの廃止

・カウントダウンの秒数を、5、10、15、20、25、30秒の中から選択できる
・オリエンテーションの種類をふやした
といったところです。

オリエンテーションの設定は、アップデートするとクリアされてAuto Rotationになりますので、ぐるぐるまわして遊んだ後は、お好みの位置に固定した方が良いと思います。

あと、波紋のようにタップ位置が表示されたり、タップした文字がボワワーンとなったりしてます。目盛りの線も前よりきれいになってます。回転している線がギザギザせずアンチエイリアシングなのは、一応、最初からのこだわりどころです。

BigStopWatch v2.2

BigStopWatchのv2.2を今日提出しました。致命的な不具合とかを見つけてしまわなければ、来週にはAppStoreにアップされると思います。

変更点は、

・Normal modeの廃止
・カウントダウンの秒数を、5、10、15、20、25、30秒の中から選択できる
・オリエンテーションの種類をふやした

という感じです。その他にも細かいアニメーションがついたりしてます。結構、Advanced modeへの変更の仕方がわからないという声が多かったので、2つモードを作るのもやっぱりめんどくさいですし、Advanced modeだけにしちゃいました。

今回のバージョンアップは、機能的にはそんなに大幅な変更ではないのですが、OpenGLでCoreAnimation的に描画するような仕組みを1から組み直してまして、90%くらいごっそりと中身は変わっています。

その成果の一部がオートローテーションで、こんな感じの動きになっています。Safariなんかのローテーションと違って、常に60フレーム弱のスムーズな描画です。

ちなみに、動いている最中もちゃんとRESET-LAPボタンそのものの位置をタップできます(たしかCoreAnimationでは出来なかったはず)。最初はいろいろ難しく考えてなかなか実現できなかったんですけど、OpenGLの描画と同じジオメトリ変換を加えたCGAffineTransformを作って、CGAffineTransformInvertでタッチしたポイントを逆変換すれば良いだけの話でした。