全般 – Mac」カテゴリーアーカイブ

Grand Central Dispatch その7 おまけ

さらに今更ながら、ASCII.jpさんの「マルチコア時代の新機軸! Snow LeopardのGCD」に、わりと一般向けの説明から始まって、コードを使用したプログラマ向けの説明までしているページがありました。とりあえずここらへんで基本知識を得てから、いろいろ試した方が良かったのかもしれません。

その記事を見ていて改めて発見したのは、dispatch_get_main_queueで、メインスレッドで実行されるキューが取得できるという事です。なるほど、これがあればperformSelectorOnMainThreadを呼ばなくてもディスパッチで呼び出しが完結できます。前回のタイマーとかはメインキュー使う方が良い気がします。

でも、こういった解説記事の中でディスパッチソースにまで触れられているものってあんまり見かけないですね。ディスパッチさえあればアプリの基本的な仕組みが作れてしまうみたいな。ということで、ソース系でもうひとつ調べてみました。

やってみたかったのは、ディスパッチでバッチ処理的な事をさせておいている途中で、キャンセルしたいって言う事です。すくなくとも僕の認識では、dispatch_asyncでブロックをキューに渡してしまうとキャンセルできない様なので、これをやりたいとおもって見つけたのがディスパッチソースの中のDISPATCH_SOURCE_TYPE_DATA_ADDというやつです。

それを使って書いてみたソースがこちらです。今回は自分でキャンセルしてみたいので、Cocoa Applicationでプロジェクトを作ってAppDelegateを以下のように変更し、Interface Builderでウィンドウにボタンをひとつ作ってAppDelegateのcancelにつなげてください。

//
// AppDelegate.h
//
#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <nsapplicationDelegate> {
    NSWindow *window;
    dispatch_source_t _source;
}
@property (assign) IBOutlet NSWindow *window;
- (IBAction)cancel:(id)sender;
@end
//
// AppDelegate.m
//
#import "AppDelegate.h"
@implementation AppDelegate
@synthesize window;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    dispatch_queue_t queue = dispatch_queue_create("test", NULL);
    _source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, queue);
    
    __block int count = 0;
    
    dispatch_source_set_event_handler(_source, ^{
        count++;
        printf("call %d\n", count);
        usleep(100000);
        dispatch_source_merge_data(_source, 1);
    });
    
    dispatch_source_set_cancel_handler(_source, ^{
        printf("end\n");
        dispatch_release(_source);
        _source = NULL;
        dispatch_release(queue);
        exit(0);
    });
    
    dispatch_source_merge_data(_source, 1);
    dispatch_resume(_source);
}
- (IBAction)cancel:(id)sender
{
    dispatch_source_cancel(_source);
}
@end

実行すると、event handlerのブロックが繰り返し呼ばれてログにカウントが表示されます。キャンセルボタンを押すと終了します。

DATA_ADDの使い方としては、dispatch_source_merge_data()の2つ目の引数で1以上の値を与えて呼ぶと、dispatch_source_set_event_handlerで登録したブロックが呼び出されるという感じになります。このサンプルでは、event_handlerのブロックの中から直接merge_dataを呼び出して処理を繰り返すようにしています。

merge_dataに0を渡してもブロックが呼ばれない事からも分かるように、たぶん本来はこれで処理自体をするものではなく、こちらのページにのっているように、dispatch_applyとかでやっている処理の進捗の量をmerge_dataの引数で受け取ってインジケータに表示するような用途に使うものだと思われます。

Grand Central Dispatch その6 timer

いまさらながら、GCDのサンプルソースはMac Dev Centerにもいろいろありますので参考に。中でもDispatch_Samplesなんかはシンプルなサンプルがたくさん集まってますので、最初に見るには良いと思います。

今回からはディスパッチの中でもソースというものを見ていこうと思います。前回のエントリにキューとソースは別なんて一度書いてしまいましたが、間違いでソースでもキューは使います。

と書いてて、ディスパッチのソースと、ソースコードの区別がわけ分からなくなりそうなので、ディスパッチのソースは「ディスパッチソース」と呼ぶ事にします。

ディスパッチソースの種類にはいろいろありますが、基本的には、何かしら外部からイベントを受け取ったらキューにブロックを渡すというもののようです。以下のようなものがあります。

DISPATCH_SOURCE_TYPE_DATA_ADD:
DISPATCH_SOURCE_TYPE_DATA_OR:
DISPATCH_SOURCE_TYPE_MACH_SEND
DISPATCH_SOURCE_TYPE_MACH_RECV
DISPATCH_SOURCE_TYPE_PROC
DISPATCH_SOURCE_TYPE_READ
DISPATCH_SOURCE_TYPE_SIGNAL
DISPATCH_SOURCE_TYPE_TIMER
DISPATCH_SOURCE_TYPE_VNODE
DISPATCH_SOURCE_TYPE_WRITE

まあ、ほとんどは自分じゃ使わないなぁと思って調べてなかったりするんで、簡単なそうなタイマーを見ていきます。まずはサンプルソースから。

#include <stdio.h>
#include <stdlib.h>
#include <dispatch/dispatch.h>
int main (int argc, const char * argv[]) {
    dispatch_queue_t queue = dispatch_queue_create("timerQueue", 0);
    //タイマーのソースを作成
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    __block int i = 0;
    //イベントを受け取った時に実行されるブロックをソースに設定する
    dispatch_source_set_event_handler(timer, ^{
        printf("%d\n", i);
        i++;
        if (i == 10) dispatch_source_cancel(timer);
    });
    //ソースがキャンセルされてときに実行されるブロックをソースに設定する
    dispatch_source_set_cancel_handler(timer, ^{
        dispatch_release(timer);
        dispatch_release(queue);
        printf("end\n");
        exit(0);
    });
    dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC); //今から1秒後
    uint64_t interval = NSEC_PER_SEC / 5; //0.2秒
    //タイマーを設定する
    dispatch_source_set_timer(timer, start, interval, 0);
    printf("start\n");
    dispatch_resume(timer);
    dispatch_main();
    return 0;
}

実行すると、ログに0から10まで数字が0.2秒おきに出力されて、アプリが終了します。

では、コードを上から見ていきます。まずは、タイマーのディスパッチソースを作成しています。ディスパッチソースの作成はdispatch_source_createです。

dispatch_source_t
dispatch_source_create(dispatch_source_type_t type,
uintptr_t handle,
unsigned long mask,
dispatch_queue_t queue);

ひとつめの引数にはディスパッチソースの種類を渡します。タイマーはDISPATCH_SOURCE_TYPE_TIMERです。2つめの引数のhandleは、ディスパッチソースが動作する元になるものを渡しますが、タイマーは必要ないので0で良いです。3つ目もタイマーでは必要ありませんので、これも0です。最後のqueueには、ディスパッチソースからブロックを渡すキューを設定します。

ちなみに、キューはグローバルでもシリアルでも使えますが、グローバルキューだと他の色んなブロックが入っていたら後回しになって影響を受けて実行されるのが遅れやすくなりそうなので、シリアルキューにして完全にセパレートされている方が良いのかなぁと思います。あくまで予測ですけど。

dispatch_source_set_event_handlerは、イベントを受け取ったときに呼ばれるブロックを設定します。今回はタイマーですので、タイマーが定期的にイベントを投げてきて、ここのブロックが呼ばれる事になります。

dispatch_source_set_cancel_handlerはディスパッチソースがキャンセルされたときに呼ばれるブロックを設定します。基本、タイマーのディスパッチソースやキューを解放します。

dispatch_source_set_timerでは、タイマーの開始時間とインターバルを指定します。スタートの時間は、dispatch_timeを使うと今から何秒後みたいな感じで絶対時間のdispatch_time_tが取得できます。インターバルは、1秒がNSEC_PER_SECで定義されていますから、そこから求めるのが良いと思います。

で、忘れていけないのは、dispatch_resumeです。ディスパッチソースは作った段階ではsuspend状態ですので、resumeで発動させなければいけません。

Grand Central Dispatch その5 semaphore

ディスパッチはいままでの並列処理の方法と比べるとシンプルで簡単だ、なんて書かれていたりしますけど、どうやらセマフォは欠かせないようで、ディスパッチにもあります。

とりあえずサンプルコードから。

#include <stdio.h>
#include <unistd.h>
#include <dispatch/dispatch.h>
int main (int argc, const char * argv[]) {
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    
    for (int i = 0; i < 10; i++) {
        dispatch_group_async(group, queue, ^{
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            //クリティカルセクション開始
            printf("start %d\n", (int)i);
            usleep(10000);
            printf("end %d\n", (int)i);
            //クリティカルセクション終了
            dispatch_semaphore_signal(semaphore);
        });
    }
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    dispatch_release(semaphore);
    dispatch_release(group);
    
    return 0;
}

ディスパッチのセマフォの使い方としてはまず、dispatch_semaphore_createでセマフォのオブジェクトdispatch_semaphore_tを作ります。引数にはセマフォの旗の数を指定できます。0だと一回も進入できなくなってしまいますので1以上を指定します。

そして、クリティカルセクションをdispatch_semaphore_wait〜dispatch_semaphore_signalで囲みます。それぞれ引数にセマフォオブジェクトを渡します。waitでは進入できない時に待つ時間を指定できて、DISPATCH_TIME_FOREVERなら永久に待ちます。

サンプルコードを実行すると、クリティカルセクション開始〜終了としているところは同時に実行されず、printfでstartとendがセットで0から9まで順番に出力されます。もし、セマフォ関連の関数を排除したら、startのあとにスリープさせていますので、startが10個出力された後に、endが10個出力されるはずです。

という感じで、ディスパッチのキューに直接ブロックを渡す関係は以上で終わりです。ちなみに、キューにブロックを渡してしまうと後からキャンセルが出来なかったりしますのでご注意を。

その処理のキャンセルとかも含めて、ディスパッチにはソースという仕組みも用意されていますので、次回以降見ていこうと思います。

Grand Central Dispatch その4 group

前回紹介したdispatch_applyはブロックの処理が終わるまで待ってくれていましたが、dispatch_asyncは別スレッドで処理されてしまうので、どの順番で処理されるのかも分からない状態でブロックを処理させっぱなしになってしまいます。

グループというのを使えば、asyncで処理する複数のブロックをひとつのグループにまとめて、それらのブロック全部が終わったタイミングを受け取る事が出来ます。

まず、グループを作る関数がこれです。

dispatch_group_t dispatch_group_create(void);

次に、グループで処理させたいブロックを渡すのがdispatch_group_asyncです。グループはasyncオンリーでsyncはありません。

void
dispatch_group_async(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);

そして、グループの処理全部が終わったら呼ばれるブロックを設定するのがdispatch_group_notifyです。基本的に、グループ内共通で使ったメモリ領域などを解放するのに使うようです。どこかに通知するのにも使えると思います。

void
dispatch_group_notify(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);

カレントスレッドで処理の終わりを待つ必要があれば、待たせることもできます。dispatch_group_waitです。どれだけ待つのかを指定できます。DISPATCH_TIME_FOREVERという定数をtimeoutに渡せば、いつまでも待ちます。

long
dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);

なお、waitで待っても待たなくても、notifyで設定したブロックが呼ばれるのは別スレッドのようです。

サンプルソースです。

#include <stdio.h>
#include <dispatch/dispatch.h>
int main (int argc, const char * argv[]) {
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    __block int sum = 0;
    
    for (int i = 0; i < 10; i++) {
        dispatch_group_async(group, queue, ^{
            printf("+%d\n", i);
            sum += i;
        });
    }
    
    dispatch_group_notify(group, queue, ^{
        printf("finish");
        //<strike>dispatch_release(group);</strike>
    });
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    printf("sum = %d\n", sum);
    dispatch_release(group);
    
    return 0;
}

前回のapplyと同じような処理をさせています。waitが無ければ、おそらくsumは0で終わってしまうと思いますが、waitで待っている事でちゃんと45と答えが出ます。

waitの代わりにdispatch_main()にしておいてnotifyのブロックの中でsumを見れば、そこでも45となっていて、ちゃんとasyncで渡したブロックが全て実行された後に呼ばれる事が分かると思います。

ちなみに、notifyのブロックが呼ばれる前にasyncで新たなブロックを同じグループに入れると、その新しいブロックも終わってからnotifyのブロックが呼ばれます。が、、notifyのタイミングが新たなブロックの前か後かどちらでくるか分からなくなってしまうので、やらない方が良いと思います。

Grand Central Dispatch その3 apply

前回のコードのように同じ処理をforループでキューに渡すようなとき、それ専用の関数がディスパッチには既に用意されています。dispatch_applyです。

void
dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));

iterationsの数だけブロックをキューに渡して処理させます。ブロックはsize_tの引数を一つだけ持ち、よくあるforループと同じように0から1ずつ増やした値が渡されます。

まずは、サンプルコードです。

#include <stdio.h>
#include <dispatch/dispatch.h>
int main (int argc, const char * argv[]) {
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    __block int sum = 0;
    
    dispatch_apply(10, queue, ^(size_t i){
        sum += i;
    });
    
    printf("sum = %d\n", sum); // sumは45になる
    
    return 0;
}

これを実行すると、0から9までがsumに足されて45になります。

dispatch_applyはグローバルキューに渡していても、ちゃんと全てのブロックが処理されるのを待ちますので、dispatch_main()が無くてもアプリが終了する前に全て足された答えが出ます。ですが、sync的に順番に処理されている訳ではなく、並列に処理はされています。ただ、asyncと同じという訳でもなく、applyでは、呼び出したスレッドと別スレッドを組み合わせて並列にブロックが実行されているようです(asyncは別スレッドだけ)。

ブロックの途中でスリープさせたりとかして試してみると、applyでは同時に2つまでしか処理されません(試したのがMacBook Airでコアが2つだから?)。asyncだといくらでも同時に実行されてしまいます。このあたりは、使い道の違いって感じですかね。

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;
}

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

ジョグホイール

iPodのクリックホイールのように、グルグルと円を描いて値を増減させる例です。NSViewのサブクラスで以下のような感じに。1周まわすと1.0増減します。

//
// YKJogWheelView.h
//

#import <Cocoa/Cocoa.h>

@interface YKJogWheelView : NSView {

    CGFloat gStartAngle;
    CGFloat gPreAngle;
    NSInteger gRotCount;
}

@end
//
// YKJogWheelView.m
//

#import "YKJogWheelView.h"

@implementation YKJogWheelView

- (id)initWithFrame:(NSRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code here.
    }
    return self;
}

- (void)drawRect:(NSRect)rect {
    // Drawing code here.
}

- (void)setValue:(CGFloat)value
{
    NSLog(@"value = %f", value);
}

- (CGFloat)getAngle:(NSEvent *)theEvent
{
    NSPoint point = [theEvent locationInWindow];
    NSRect frame = [self frame];
    return -atan2(point.y - frame.origin.y - frame.size.height / 2, point.x - frame.origin.x - frame.size.width / 2) / M_PI / 2.0;
}

- (void)setDragEvent:(NSEvent *)theEvent
{
    CGFloat newAngle = [self getAngle:theEvent];
    
    if ((newAngle - gPreAngle) > 0.5) {
        gRotCount--;
    } else if ((newAngle - gPreAngle) < -0.5) {
        gRotCount++;
    }
    
    [self setValue:newAngle - gStartAngle + gRotCount];
    
    gPreAngle = newAngle;
}

- (void)mouseDown:(NSEvent *)theEvent
{
    gStartAngle = [self getAngle:theEvent];
    gRotCount = 0;
    [self setValue:0];
}

- (void)mouseDragged:(NSEvent *)theEvent
{
    [self setDragEvent:theEvent];
}

@end

24ビット

24bitのオーディオデータを作ったり読みこもうとしてもObjective-C(というかC言語)では24bitの型がありませんので、符号付き32bit整数の上位24bit分を使うというのが簡単な方法ではないかと思います。

ただこの場合、32bitの下位8bitは単純に切り捨てられますので、音にこだわるのであればディザをかけたりしたほうが良いのかもしれませんが、とりあえず今回は無視します。

32bit領域のAlignedHighなデータとして渡したりするのであれば何もしなくてもいいですが、32bit領域でAlignedLowな24bitのデータにしなくちゃいけないなんて時があったりしたら、

SInt32 value;

という、あるオーディオのデータがあったとして、

value >>= 8;

としてやります。

処理系によって算術シフトになるか論理シフトが変わるらしいですが、Xcodeでgccを使っているのであればこの場合算術シフトで、valueがマイナスのときは上位8bitが1で埋め尽くされています。

ちなみに、IntelMac想定の検証コード。

#import <Foundation/Foundation.h>

void logBit (SInt32 value) {
    printf("MSB ");
    for (NSInteger i = 3; i >= 0; i--) {
        Byte *valuePtr = (Byte *)&value;
        for (NSInteger j = 7; j >= 0; j--) {
            SInt32 isBitOn = (valuePtr[i] & (1 << j)) ? YES : NO;
            printf("%d", isBitOn);
        }
        printf(" ");
    }
    printf(" LSB\n\n");
}

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    SInt32 value = INT32_MIN;
    printf("value = %d\n", value);
    logBit(value);
    
    SInt32 sValue = value;
    sValue >>= 8;
    printf("signed shift value = %d\n", sValue);
    logBit(sValue);
    
    UInt32 uValue = (UInt32)value;
    uValue >>= 8;
    printf("unsigned shift value = %u\n", uValue);
    logBit(uValue);
    
    [pool drain];
    return 0;
}

実行すると…

value = -2147483648
MSB 10000000 00000000 00000000 00000000  LSB

signed shift value = -8388608
MSB 11111111 10000000 00000000 00000000  LSB

unsigned shift value = 8388608
MSB 00000000 10000000 00000000 00000000  LSB

という感じになります。

リサージュ波形を表示する

オシロスコープで表示できるようなリサージュ波形を描画するには、

x = sin(右チャンネルのオーディオデータ);
y = sin(左チャンネルのオーディオデータ);

という感じでサンプルごとに座標を求めて、線で繋ぐ。

NSViewのサブクラスでこのようなインスタンス変数があるとして、

NSUInteger length; //オーディオデータのサンプル数
float *lPtr; //左チャンネルのオーディオデータ
float *rPtr; //右チャンネルのオーディオデータ

以下のようにdrawRectメソッドを記述する。(※2008/7/8 描画する位置をrectではなくboundsから求めるように変更しました)

- (void)drawRect:(NSRect)rect {
    
    NSRect viewRect = [self bounds];
    double halfWidth = viewRect.size.width / 2.0;
    double halfHeight = viewRect.size.height / 2.0;
    
    [[NSColor blackColor] set];
    NSRectFill(
        NSMakeRect(0, 0, viewRect.size.width, viewRect.size.height));
    
    NSBezierPath *path = [NSBezierPath bezierPath];
    [[NSColor greenColor] set];
    [path setLineWidth:1.0];
    
    for (NSUInteger i = 0; i < length; i++) {
        
        double x = sin(rPtr[i]);
        double y = sin(lPtr[i]);
        
        NSPoint point = 
            NSMakePoint(x * halfWidth + halfWidth , 
                y * halfHeight + halfHeight);
        if ([path isEmpty]) {
            [path moveToPoint:point];
        }
        
        [path lineToPoint:point];
    }
    
    [path stroke];
}