Objective-C スレッドと libuv スレッドとのやりとり
libuv 専用スレッドと通常の Objective-C スレッドとのやりとりの仕方、ついでに書いておく。
tl;dr - libuv スレッドから Objective-C スレッドに対して何か送るときは Objective-C の作法がそのまま使える。逆は uv_async
を使う
libuv スレッドから Objective-C スレッド
これは Objective-C の世界の作法がそのまま使える。
iOS4+ と OSX 10.6+ であれば GCD で、
dispatch_async(dispatch_get_main_queue(), ^{
// ここはメインスレッド
});
のようなのを書くだけでメインスレッドの動作を定義できるから、そこでメソッド呼ぶなり Notification を発行するなりすれば良いので楽ちん。
それ以下の環境だったら performSelector:onThread:withObject:waitUntilDone:
系のを使う。
Objective-C スレッドから libuv スレッド
libuv スレッドでは NSRunLoop が回ってないので上記の作法は使えない。代わりに uv_async
を使う。
まず、libuv スレッドで async コールバックを設定:
static void async_cb(uv_async_t* handle, int status) {
}
uv_async_init(self->loop, &self->async, async_cb);
で、呼び出し側(Objective-Cスレッド)から
uv_async_send(&obj->async);
とすれば async_cb
が libuv スレッドで発動するという寸法。
データを渡したいときは
@synchronized (obj.send_queue) {
[obj.send_queue addObject:@"foo"];
}
uv_async_send(&obj->async);
という感じにしておいて、取り出す側も
static void async_cb(uv_async_t* handle, int status) {
NSArray* queue;
@synchronized (self.send_queue) {
queue = [NSArray arrayWithArray:self.send_queue];
[self.send_queue removeAllObjects];
}
// 処理
}
みたいにすれば良い。
async コールバックは複数作ることができるから、用途に応じてコールバックを使い分けるのがよさそう。(データ送信用、スレッド終了用など)
libuv (libev) と Objective-C autorelease のはまりポイント
iOS や Mac アプリで HTTP 以外のネットワーク機能をつけたいといった場合に、libuv や libev を組み込んで使うというのを割とよくする。方法としては以下のような感じでその機能用のスレッドをつくる:
-(void)run {
NSThread* thread = [[NSThread alloc] initWithTarget:self
selector:@selector(loop)
object:nil];
self.thread = thread;
[thread release];
[thread start];
}
スレッドの中身は大体こんな感じ:
-(void)loop {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
uv_loop_t* loop = uv_loop_new();
// いろいろ初期化
// ...
// libuv イベントループ
uv_run(loop);
uv_loop_delete(loop);
[pool drain];
}
このスレッドは uv_run
でブロックしてしまう。本来ここではCocoaのイベントループ(NSRunLoop)をまわす部分だが、かわりに libuv のイベントループを回している感じになっている。
したがってこのスレッドで Objective-C を混ぜる場合には autorelease がスレッド終了まで基本的にされなくなるから注意が必要。
これを解決する方法で最初に思いつくのは、uv_run
(ev_run
) のかわりに uv_run_once
(ev_run(..., EVRUN_ONCE)
) を使うことだ。
while (1) {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
uv_run_once(loop);
[pool drain];
}
これは一見簡単だけど while ループをとめるフラグを別途用意しないといけないし、uv_run
とちがってループを抜けるときには各イベントハンドラが終了しているかを確かめる必要もありめんどくさい。
結果いまはこんな感じにしている:
static void idle_cb(uv_idle_t* handle, int status) {
uv_idle_stop(handle);
[(NSAutoreleasePool*)handle->data drain];
handle->data = NULL;
}
static void check_cb(uv_check_t* handle, int status) {
uv_idle_t* idle = (uv_idle_t*)handle->data;
if (NULL != idle->data) return;
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
idle->data = (void*)pool;
uv_idle_start(idle, idle_cb);
}
-(void)loop {
uv_loop_t* loop = uv_loop_new();
uv_check_t check;
uv_check_init(loop, &check);
uv_check_start(&check, check_cb);
uv_idle_t idle;
uv_idle_init(loop, &idle);
check.data = (void*)&idle;
idle.data = NULL;
// いろいろ初期化
// ...
uv_run(loop);
uv_loop_delete(loop);
}
uv_check
(ev_check
) で NSAutoreleasePool
をつくりつつ idle タイマーを作って、 その idle タイマー時に [pool drain]
する。 これだとイベントが詰まっている場合は drain は呼ばれず、キリの良いときに呼んでくれるから run_once
でいちいちやるよりは効率も良さそうな気がする。
このコードでは省略しているけど実際に使うときには check
や idle
も終了しないと uv_run
から抜けてこないのでどこかのオブジェクトにまとめて突っ込んでおいたりして使うのが吉。
こういうのを何も考えずに Objective-C をまぜるともりもりメモリ食うようになるから気をつけよう!
Objective-C で AUTOLOAD (あるいは method_missing )
forwardInvocation
とかを使えば出来るみたい。
#import <Foundation/Foundation.h>
@interface Foo : NSObject;
-(void)call:(NSString*)sel;
@end
@implementation Foo
-(void)call:(NSString*)sel {
NSLog(@"call: %@", sel);
}
-(void)forwardInvocation:(NSInvocation *)invocation {
[self call:NSStringFromSelector([invocation selector])];
}
-(NSMethodSignature*)methodSignatureForSelector:(SEL)sel {
NSMethodSignature* sig = [super methodSignatureForSelector:sel];
if (sig) return sig;
return [[self class] instanceMethodSignatureForSelector:@selector(call:)];
}
@end
int main() {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
Foo* foo = [[Foo alloc] init];
[foo bar];
[foo buzz];
[foo release];
[pool drain];
return 0;
}
// gcc -framework Foundation foo.m
詳細は Objective-C Runtime Programming Guide にある。
PerlのXSでObjective-Cを使うときに気をつけること
QuickDraw API で定義されている Move
マクロが、perl の handy.h の中で定義されているものとコンフリクトするため、
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "ppport.h"
// undefine Move macro, this is conflict to Mac OS X QuickDraw API.
#undef Move
#import <Foundation/Foundation.h>
こんなようにしてperl関係のヘッダーをincludeしたあと#undef
してあげたうえでCocoaのヘッダーをimportするようにすると良い。
なお、 Foundation 以外にリンクするときはまた別の問題があるかもしれない。
基本XS使うときは、まず単体でうごくCやObjective-Cのコードを書いて、XSはそれをつなぐだけという感じで使っているので、ガチでXS内でごりごり処理を実装することはなく、perlのMoveマクロは使ったことないから、それを使いたいときにどうすればいいかは知らない。
memmoveのラッパーみたいだけど、NewxやSafefreeなどのようにこれ使ったほうが良いとかたぶんあるんだろうけど。
PerlエンジニアのためのObjective-C Blocks入門
OS X 10.6 以降の xcode では Objective-C に Blocks というシンタックスが追加されている。
Perl でいうところの無名関数(コードブロック)を作ることができる機能で、Perlでいうところの
my $f = sub { ... };
$f->();
は
void (^f)() = ^{ ... };
f();
のように書ける。書式がきもいのはObjective-Cの定めなのであきらめましょう。 より詳しい書式については上記ドキュメントを見ると良い。
しかしデフォルトではPerlのコードブロックとはレキシカル変数の扱いが異なる。
my $i = 0;
my $f = sub { return $i + 1 };
$i++;
$f->(); # 2 を返す
perlではこのようにレキシカル変数はコードブロック内と共有されるが、Objective-Cの場合は
int i = 0;
int (^f)() = ^{ return i + 1; };
i++;
f(); // 1 を返す
このようになる。これはブロックを作成するときのレキシカル変数がコピーされるからである。 Perlとおなじような挙動を望む場合は __block
ストレージタイプを指定すればいいようだ。
__block int i = 0;
int (^f)() = ^{ return i + 1; };
i++;
f(); // 2
ここまでわかればObjective-CでBlockの再帰を書くことができる。
__block void (^f)();
f = ^{ f(); };
f();
これは以下とおなじ、
my $f;
$f = sub { $f->() };
$f->();
というようにPerlエンジニアにとっては割と直感的なコードブロックが使えるようになっております。 書式がきもいことをのぞけばいい感じです。
次回は「PerlエンジニアのためのGrand Central Dispatch」の予定です。
追記@2010-05-11T19:12:48+09:00
はてなブックマーク - PerlエンジニアのためのObjetive-C Blocks - unknownplace.org
lyokato 「Objective-Cの仕様というよりは、Blocks拡張に対応したCコンパイラの仕様かな」
ってことでやってみたら
#include <stdio.h>
int main(int argc, char** argv) {
void (^f)() = ^{ printf("Hello Blocks!\n"); };
f();
return 0;
}
// $ gcc foo.c
// $ ./a.out
でも行けました。gcc の拡張なんですね。あざっす!