【发布时间】:2013-04-22 22:14:29
【问题描述】:
我正在尝试使用 NSRunLoop 来监视代理中的 FSEvents 应用程序(即没有任何 GUI)。我认为我了解如何 RunLoop 有效,但我显然没有,因为我看到的行为 是不可理解的。我错过了什么? (我对线程很满意 用几种语言编程,但 Objective-C 有点新奇 对我来说)。
下面复制的是(我能理解的)最小实现
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 案例中是正确的。在每一个
我希望在pathsSeen=0 行之前看到 5 秒的延迟
出现,作为 RunLoop,没有看到任何 FSEvents,阻塞到末尾
waitEnd 区间。
这两个特点都表明我误解了一些东西 非常基础,大概是关于对象的生命周期。我觉得我可以 说明以下各项:
- 我确实应该致电
NSRunLoop runMode:beforeDate程序的主线程(程序没有别的 等待时做,所以被阻止是正确的 事物)。这与 RunLoops 中的解释兼容 Threading Programming Guide。 - 每个线程只有一个 RunLoop,所以我正在调度
event_stream在我等待的 RunLoop 上。 - 我拥有
event_stream,根据创建规则,所以这不是 在我背后回收。 -
waitEnd每次循环都会不同——也就是说,它是 没有从一个通道保存到另一个通道。 - 用
createStream:runLoop初始化一个instance 变量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 调用的内置计时器有关。 This question on FSEvent and 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 上。
(这是一个很长的问题:抱歉。通常在您问 这么长的问题,您已经为自己找到了答案,但是 – 不 – 我现在和以前一样困惑。)
【问题讨论】:
标签: objective-c cocoa nsthread fsevents