NSRunLoop を使用して、エージェント アプリケーションで (つまり、GUI を使用せずに) FSEvents を監視しようとしています。RunLoop の仕組みは理解できたと思いますが、理解できないのは明らかです。私は何が欠けていますか?(私はいくつかの言語でのスレッド プログラミングに慣れていますが、Objective-C は私にとって少し目新しいものです)。
EventHandler
以下にコピーしたのは、クラスの最小限の実装です (私が入手できる限り)
。これは、インスタンスを割り当てて初期化することによりメイン関数から呼び出され、
次に 、最後にを使用してstartWatching
メッセージを送信します。"/tmp/fussybot-test"
tidyUp
以下の実装コードは、デフォルトの RunLoop にアタッチされたイベント ストリームを作成、スケジュール、および開始し、ループして、FSEvents を待機するrunMode:beforeDate
か、RunLoop のタイマーの期限が切れるのを待ちます。
#import "EventHandler.h"
void mycallback(ConstFSEventStreamRef streamRef,
void *userData,
size_t numEvents,
void *eventPaths,
const FSEventStreamEventFlags eventFlags[],
const FSEventStreamEventId eventIds[])
{
EventHandler *eh = (__bridge EventHandler*)userData;
size_t i;
char **paths = eventPaths;
NSLog(@"callback: %zd events to process...", numEvents);
for (i=0; i<numEvents; i++) {
NSLog(@"Event %llu in %s (%x)", eventIds[i], paths[i], eventFlags[i]);
[eh changedPath:paths[i]];
}
}
@implementation EventHandler
-(void) startWatching: (NSString*) path
{
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
[self createStream:path runLoop:theRL];
BOOL recentFSActivity_p = YES;
while (recentFSActivity_p) {
NSDate* waitEnd = [NSDate dateWithTimeIntervalSinceNow:5.0];
//NSLog(@"waiting until %@", waitEnd); // XXX
if (! [theRL runMode:NSDefaultRunLoopMode
beforeDate:waitEnd]) {
NSLog(@"the run loop could not be started");
}
int ps = [self pathsSeen];
NSLog(@"Main loop: pathsSeen=%i", ps);
if (ps == 0) {
recentFSActivity_p = NO;
}
}
}
- (void) tidyUp
{
FSEventStreamStop(event_stream);
FSEventStreamInvalidate(event_stream);
return;
}
- (FSEventStreamRef) createStream: (NSString*) path
runLoop: (NSRunLoop*) theRL
{
pathsToWatch = [NSArray arrayWithObject:path];
FSEventStreamContext context = {0, (__bridge void*)self, NULL, NULL, NULL};
CFAbsoluteTime latency = 3.0; /* Latency in seconds */
/* Create the stream, passing in a callback */
event_stream = FSEventStreamCreate(NULL,
&mycallback,
&context,
(__bridge CFArrayRef) pathsToWatch,
kFSEventStreamEventIdSinceNow,
latency,
kFSEventStreamCreateFlagNone);
FSEventStreamScheduleWithRunLoop(event_stream,
[theRL getCFRunLoop],
kCFRunLoopDefaultMode);
FSEventStreamStart(event_stream);
return event_stream;
}
-(void)changedPath:(char *)path
{
NSLog(@"Path %s changed", path); // log that we got here
nchangedPaths += 1; // ...and count the number of calls
}
-(int)pathsSeen
{
int n = nchangedPaths; // return instance variable
nchangedPaths = 0; // ...and reset it
return n;
}
@end
では、それをビルドして開始し、監視されているディレクトリ内のファイルをタッチします。
% make && ./fussybot & date '+NOW: %T'; sleep 2; echo hello >/tmp/fussybot-test/hello.txt
cc -c -x objective-c -fobjc-arc -o EventHandler.o EventHandler.m
cc -o fussybot main.o EventHandler.o -framework Cocoa
[1] 57431
NOW: 22:56:54
% 2013-04-22 22:56:57.692 fussybot[57431:707] callback: 1 events to process...
2013-04-22 22:56:57.694 fussybot[57431:707] Event 645428112 in /private/tmp/fussybot-test/ (11400)
2013-04-22 22:56:57.694 fussybot[57431:707] Path /private/tmp/fussybot-test/ changed
2013-04-22 22:56:57.695 fussybot[57431:707] Main loop: pathsSeen=1
2013-04-22 22:56:57.695 fussybot[57431:707] Main loop: pathsSeen=0
Exiting...
[1] + done ./fussybot
%
次に、行
(上でマークされている) のコメントを外して、もう一度試します。NSLog(@"waiting until %@", waitEnd);
XXX
% make && ./fussybot & date '+NOW: %T'; sleep 2; echo hello >/tmp/fussybot-test/hello.txt
cc -c -x objective-c -fobjc-arc -o EventHandler.o EventHandler.m
cc -o fussybot main.o EventHandler.o -framework Cocoa
[1] 57474
NOW: 22:59:01
2013-04-22 22:59:01.190 fussybot[57474:707] waiting until 2013-04-22 21:59:06 +0000
2013-04-22 22:59:01.190 fussybot[57474:707] Main loop: pathsSeen=0
Exiting...
[1] + done ./fussybot
%
ここには、非常に奇妙なことが2 つあります。
NSLog
まず、呼び出しを追加すると、プログラムの動作が変わります。えっ!?- 次に、どちらの例でも、RunLoop は FSEvent を待たずにすぐに終了するように見えます。
1つ目に関しては、そのNSLog
ような効果があるという事実は確かに私に非常に重要なことを教えてくれますが、私は一生何を理解することはできません.
2 番目については、それぞれのpathsSeen=0
ケースで
runMode:beforeDate
、RunLoop オブジェクトのメッセージは をブロックせずに を返しますがYES
、このメッセージのドキュメントには、YES
「実行ループが実行されて入力ソースを処理した場合、または指定されたpathsSeen=0
上記のケースではどちらも当てはまりません。これらのケースのそれぞれで、pathsSeen=0
ラインが表示される前に 5 秒の遅延が発生することが予想されます。RunLoop は FSEvents をまったく認識せず、waitEnd
間隔の終わりまでブロックするためです。
これらの特徴はどちらも、おそらくオブジェクトの寿命について、かなり基本的なことを誤解していることを示唆しています。それぞれ次のように言えると思います。
- 私は確かに
NSRunLoop runMode:beforeDate
プログラムのメインスレッドで呼び出すことが期待されています (プログラムは待機中に他に何もすることがないので、ブロックされることはまさに正しいことです)。これは、 Threading Programming Guideの RunLoops の説明と互換性があります。 - スレッドごとに 1 つの RunLoop しかないため、待機中の RunLoop で をスケジュールし
ています。
event_stream
- Create Rule により、私は を所有
event_stream
しているため、背後で回収されることはありません。 waitEnd
つまり、パスごとに保持されません。- インスタンス変数
を
createStream:runLoop
初期化するということは、この引数でストリームを作成した後にこれが消えることを心配する必要がないことを意味します。これがローカル変数である場合、ARC 管理はメソッドの最後でこれを再利用しますが、そうではありません。これはインスタンス変数だからです。pathsToWatch
FSEventStreamCreate
- RunLoop のブロックを解除する他のイベントはありません。OS がこの RunLoop で何かをスケジュールしたとしても (ドキュメントでは慎重にそれを除外していないようです)、コールバックでそのようなイベントが表示されます。
- 最初の
FSEventStreamEventFlags
ケースのイベントは予期されたものです。何らかの理由でイベントがドロップされたことを示唆するものは何もありません。
つまり、これが機能しないことを証明したようです。それは明らかにうまくいかないので...私が壊滅的に得られないのは何ですか? (そして、私がそれを手に入れたとき、強打で、それは痛いですか?)。
FSEvent API は、Threading Programming Guide内の「The Run Loop Sequence of Events」という用語で、「ポートベースの入力ソース」を表していますか? その場合、そのシーケンスのステップ 7 で FSEvent を確実に受信する必要があります。
上記のコードは、 File System Events API ドキュメントのサンプル コードに厳密に基づいています。私の理解はこの思慮深い回答の説明と互換性があると思いますが、関連する RunLoop の他の多くの質問を見つけることができませんでした。SO システムが提案する質問は、主に、RunLoop 呼び出しの組み込みタイマーを使用するのではなく、NSTimers を具体的に追加することに関するものです。 FSEvent と Dropbox に関するこの質問は可能性が高いように見えますが、(a) 回答がなく、(b) Dropbox とのやり取りである可能性があります。
これは
% cc --version
Apple clang version 4.0 (tags/Apple/clang-421.0.60) (based on LLVM 3.1svn)
OS X 10.8.3 で。
(これは長い質問です。申し訳ありません。通常、これほど長い質問をする頃には、自分で答えを導き出していますが、いや、以前と同じように困惑しています。)