Я пытаюсь использовать 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.
(Это длинный вопрос: извините Обычно к тому времени, когда вы спросите вопрос так долго, как вы выработанный ответ для себя, но - Нету - я усложненный теперь, как я был раньше. .)
Спасибо, Кен. Я думал, что исключил бы активность из других рамок на том основании, что, если бы такая деятельность произошла, мой обратный вызов услышал бы об этом. Но является ли реальная ситуация, что этот обратный вызов вызывается потоком событий FSEvents, который затем (отдельно) передает событие в RunLoop, который не делает ничего, кроме разрешения выхода RunLoop? Однако [FSEvents docs] (https://developer.apple.com/library/mac/#documentation/Darwin/Reference/FSEvents_Ref/Reference/reference.html), похоже, говорят, что обратный вызов получен «из их runloop». Это, вероятно, не имеет значения, но ... –
И снова ... Я поменял свой код так, чтобы он создавал и планировал таймер в режиме по умолчанию. Тем не менее, я все еще не уверен, кто/где/что увольняет мой обратный вызов, но я позволю этому поработать на данный момент. То, что цикл запуска является общим, в том, как вы описываете, является довольно ключевым битом информации, которую я бы долгое время не собирал из документов NSRunLoop (я склонен сообщать об ошибке документа в Apple bugparade) , Также забавно, что ключевыми особенностями NSRunLoop являются то, что это не цикл (он вызывается в цикле), и он не запускается (он блокирует). Спасибо за указатель. –
Добавлено как проблема 13719849 в Apple bugparade (не то, что кто-то извне Купертино может видеть это ...) –