应用程序(即没有任何GUI).我想我明白了
RunLoop有效,但我显然没有,因为我看到的行为
是不可理解的.我错过了什么? (我对线程很满意
用几种语言编程,但Objective-C有点新奇
为了我).
下面复制的是(尽可能得到它)最小化的实现
EventHandler类.这是从主函数调用的
分配和初始化一个实例,然后发送一个
使用“/ 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}; 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行(@“等到%@”,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具有这样的效果的事实是
肯定告诉我一些非常重要的事情,但我不能为生活而努力
我找出了什么.
关于第二个,在每个pathSeen = 0的情况下,
runMode:RunLoop对象上的beforeDate消息没有阻塞,
但是返回YES,即使此消息的文档说明了
它只返回YES“如果运行循环运行并处理输入
来源或达到指定的超时值“,两者都没有
这在pathSeen = 0例中是正确的.在每一个
我希望在pathsSeen = 0 line之前看到5s延迟
因为RunLoop没有看到任何FSEvent,所以会出现阻塞
waitEnd间隔.
这两个特点都表明我误解了一些东西
相当基本的,大概是关于对象的生命周期.我觉得我可以
说明以下各项:
>我确实希望在之前调用NSRunLoop runMode:beforeDate
程序的主线程(该程序没有任何其他内容
在等待的同时做,所以被阻挡是正确的
事情).这与RunLoops的解释兼容
Threading Programming Guide
>每个线程只有一个RunLoop,所以我正在安排
RunLoop上的event_stream我正在等待.
>我通过创建规则拥有event_stream,因此不存在
在我背后回收.
>每次循环循环时waitEnd都会不同 – 即,它就是
没有保留从传球到传球.
>拥有createStream:runLoop初始化一个实例变量
pathsToWatch意味着我不必担心这种消失
在FSEventStreamCreate用此创建流之后
论点. ARC管理层将在年底回收这一计划
方法,如果这是一个局部变量,但不是,因为它是一个
实例变量.
>没有其他事件会导致RunLoop
解除封锁.即使操作系统确实在这个RunLoop上安排了一些东西
(文件似乎小心翼翼地不排除这一点),我
在回调中看到这样的事件.
>第一种情况下事件中的FSEventStreamEventFlags是预期的 –
没有什么可以暗示任何事件已被删除
某些原因.
也就是说,我似乎已经证明这不可行.它
明显不起作用,所以…它是什么我是灾难性的
没得到? (当我确实得到它时,会发出一声巨响
伤害?).
FSEvent API是否代表“基于端口的输入源”
“运行循环事件序列”的术语
Threading Programming Guide?
如果是这样,肯定应该在该序列的第7步中接收FSEvent.
上面的代码非常基于示例代码
在File System Events API
documentation.
我认为我的理解与this
thoughtful answer中的解释是一致的,但是
我找不到许多其他相关的RunLoop问题. SO系统建议的问题主要与专门添加NSTimers而不是使用RunLoop调用的内置计时器有关. @L_404_4@很可能,但(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上.
(这是一个很长的问题:对不起.通常在你问的时候
问这个问题,你已经为自己找到了答案,但是
– 不 – 我现在和以前一样困惑.)
解决方法
如果要以只触发源和计时器的方式运行运行循环,则需要以自定义模式计划源和计时器,并在该模式下运行运行循环.模式实际上只是一个字符串,所以使用像@“com.yourcompany.yourproduct.yourmodename”这样的东西或类似的保证是唯一的东西,你会没事的.
或者您可以简单地编写代码来应对这样一个事实,即并非所有在运行循环中触发的源都是您的.如果要检测超时到期,请安排计时器设置标志并停止运行循环.保持循环直到设置标志.我认为您可以使用计时器方法中的CFRunLoopStop()来强制-runMode:beforeDate:返回,但如果没有,则可以使用-performSelector …方法之一,这些方法可以采用线程或延迟来执行此操作.