4

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 管理はメソッドの最後でこれを再利用しますが、そうではありません。これはインスタンス変数だからです。pathsToWatchFSEventStreamCreate
  • 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 で。

(これは長い質問です。申し訳ありません。通常、これほど長い質問をする頃には、自分で答えを導き出していますが、いや、以前と同じように困惑しています。)

4

1 に答える 1

3

実行ループは共有リソースです。フレームワークは、特にデフォルト モードで、実行ループで独自の実行ループ ソースをスケジュールできます。-runMode:beforeDate:が返さYESれ、ソースの 1 つを処理しなかった場合は、フレームワークによってスケジュールされたものを処理したと考えられます。

ソースとタイマーのみが起動するような方法で実行ループを実行する場合は、カスタム モードでソースとタイマーをスケジュールし、そのモードで実行ループを実行する必要があります。モードは事実上単なる文字列なので、次のようなものを使用するか、@"com.yourcompany.yourproduct.yourmodename"同様に一意であることが保証されているものを使用すれば問題ありません。

または、実行ループで起動するすべてのソースが自分のものになるわけではないという事実に対処するために、単純にコードを作成することもできます。タイムアウトの期限切れを検出する場合は、タイマーをスケジュールしてフラグを設定し、実行ループを停止します。フラグが設定されるまでループを続けます。CFRunLoopStop()タイマーメソッドから使​​用して強制的-runMode:beforeDate:に戻ることができると思いますが、そうでない場合は-performSelector...、スレッドまたは遅延を使用してそれを行うメソッドのいずれかを使用できます。

于 2013-04-23T00:03:04.493 に答える