2013-04-22 2 views
4

Я пытаюсь использовать NSRunLoop для просмотра FSEvents в приложении Agent (то есть без какого-либо графического интерфейса). Я думаю, Я понимаю, как работает RunLoop, но я этого явно не понимаю, потому что поведение, которое я вижу, непонятно. Что мне не хватает? (Мне нравится программирование на на нескольких языках, но Objective-C - это немного новинка для меня).NSRunLoop возвращается раньше, прежде чем любой FSEvent или таймер срабатывает

Скопировано ниже (рядом, как я могу это получить) минимальная реализация класса EventHandler. Это вызвано из основной функции , выделяя и инициализируя экземпляр, затем отправляя сообщение startWatching с "/tmp/fussybot-test", а затем, наконец, tidyUp.

код реализации ниже создает, расписание и запускает поток событий прикрепленного по умолчанию RunLoop, затем петли, ожидая с runMode:beforeDate на любых FSEvents, или по истечению таймера в 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 
% 

Сейчас два чрезвычайно странные вещи здесь.

  • Во-первых, добавление вызова NSLog изменяет поведение программы. Эх ?!
  • Во-вторых, в обоих примерах RunLoop появляется, чтобы немедленно выйти, не дожидаясь FSEvent.

Что касается первых, то, что NSLog имеет такой эффект, как это , безусловно, говорит мне что-то очень важное, но я не могу за жизнь меня работать что.

Что касается второго, в каждом из pathsSeen=0 случаев runMode:beforeDate сообщения на объекте RunLoop не блокирует, но возвращает YES, хотя документация для этого сообщения говорит , что она возвращает YES только «если цикл запуска выполнялся и обрабатывался входной источник или если было достигнуто указанное значение таймаута », ни один из , который является истинным в вышеприведенных случаях pathsSeen=0. В каждом из этих случаев я ожидаю увидеть задержку 5 секунд до того, как появится pathsSeen=0 строка , так как RunLoop, не видя никаких FSEvents, блокирует до конца интервал waitEnd.

Обе эти особенности предполагают, что я что-то недопонимаю довольно фундаментальный, предположительно о жизни объекта. Я думаю, что могу состояние каждого из следующих действий:

  • Я действительно ожидал позвонить NSRunLoop runMode:beforeDate на основной поток программы (программа не имеет ничего другого делать во время ожидания, поэтому блокируется это точно вещь). Это совместимо с объяснением RunLoops в Threading Programming Guide.
  • В одном потоке есть только один RunLoop, поэтому я am, планируя event_stream на RunLoop, которого я жду.
  • Я владею event_stream, создав правило, так что это не , восстановленный за моей спиной.
  • waitEnd будет отличаться каждый раз по кругу - то есть, это не сохранился от прохода до прохода.
  • Имея createStream:runLoopопределения свойств экземпляру переменной pathsToWatch означает, что я не придется беспокоиться об этом исчезает после FSEventStreamCreate создал поток с этим аргументом. Управление ARC вернет это в конце метода , если это была локальная переменная, но это не так, потому что это переменная экземпляра .
  • Нет никаких других событий, которые могут привести к разблокированию RunLoop до . Даже если ОС планировала что-то в этом RunLoop (документация, похоже, тщательно не исключает этого), я бы видел такое событие в обратном вызове.
  • FSEventStreamEventFlags на мероприятии в первом случае ожидаются номера - ничего, что могло подсказать по каким-либо причинам .

То есть, кажется, я доказал, что это не может работать. Это явно не работает, так что ... что это я катастрофически не получилось? (и когда я это получу, с треском, это будет больно?).

Представляет ли FSEvent API в «основе порта источника входного сигнала», в терминах «The Run Loop последовательности событий» в Threading Programming Guide? Если это так, то FSEvent должен быть принят на шаге 7 этой последовательности.

Приведенный выше код тесно связан с примером кода в File System Events API documentation. Я думаю, что мое понимание совместимо с объяснениями в this thoughtful answer, но Мне не удалось найти много других релевантных вопросов RunLoop. Вопросы, которые предлагает система SO, в основном связаны с конкретным добавлением NSTimers, а не с использованием таймера встроенного вызова RunLoop. This question on FSEvent and Dropbox выглядит скорее, но (а) остается без ответа, и (б) может быть взаимодействие с Dropbox.

Это

% cc --version 
Apple clang version 4.0 (tags/Apple/clang-421.0.60) (based on LLVM 3.1svn) 

на OS X 10.8.3.

(Это длинный вопрос: извините Обычно к тому времени, когда вы спросите вопрос так долго, как вы выработанный ответ для себя, но - Нету - я усложненный теперь, как я был раньше. .)

ответ

3

Пробег представляет собой общий ресурс. Структуры могут и планируют собственные источники цикла запуска в цикле выполнения, особенно в режиме по умолчанию. Если -runMode:beforeDate: возвращает YES, и он не обрабатывал один из ваших источников, то он предположительно обрабатывал один из запланированных фреймворками.

Если вы хотите запустить цикл запуска таким образом, чтобы только запускали ваши источники и таймеры, вам необходимо запланировать источники и таймеры в специальном режиме и запустить цикл цикла в этом режиме. Режим - это просто строка, поэтому используйте что-то вроде @"com.yourcompany.yourproduct.yourmodename" или что-то подобное гарантировано, чтобы быть уникальным, и с вами все будет в порядке.

Или вы могли бы просто написать свой код, чтобы справиться с тем, что не все источники, запускающие цикл запуска, будут вашими. Если вы хотите определить истечение таймаута, назначьте таймер, чтобы установить флаг и остановить цикл выполнения. Продолжайте цикл до тех пор, пока не будет установлен флаг. Я думаю, вы можете использовать CFRunLoopStop() из своего метода таймера, чтобы вернуть -runMode:beforeDate:, но если нет, вы можете использовать один из методов -performSelector..., которые либо берут поток, либо задерживают его.

+0

Спасибо, Кен. Я думал, что исключил бы активность из других рамок на том основании, что, если бы такая деятельность произошла, мой обратный вызов услышал бы об этом. Но является ли реальная ситуация, что этот обратный вызов вызывается потоком событий FSEvents, который затем (отдельно) передает событие в RunLoop, который не делает ничего, кроме разрешения выхода RunLoop? Однако [FSEvents docs] (https://developer.apple.com/library/mac/#documentation/Darwin/Reference/FSEvents_Ref/Reference/reference.html), похоже, говорят, что обратный вызов получен «из их runloop». Это, вероятно, не имеет значения, но ... –

+0

И снова ... Я поменял свой код так, чтобы он создавал и планировал таймер в режиме по умолчанию. Тем не менее, я все еще не уверен, кто/где/что увольняет мой обратный вызов, но я позволю этому поработать на данный момент. То, что цикл запуска является общим, в том, как вы описываете, является довольно ключевым битом информации, которую я бы долгое время не собирал из документов NSRunLoop (я склонен сообщать об ошибке документа в Apple bugparade) , Также забавно, что ключевыми особенностями NSRunLoop являются то, что это не цикл (он вызывается в цикле), и он не запускается (он блокирует). Спасибо за указатель. –

+0

Добавлено как проблема 13719849 в Apple bugparade (не то, что кто-то извне Купертино может видеть это ...) –

Смежные вопросы