我試圖使用NSRunLoop監視代理程序 應用程序(即沒有任何GUI)中的FSEvent。我認爲我明白一個 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
以上的情況都是如此。在這些 的每一種情況下,我都希望在出現pathsSeen=0
行 之前看到5秒的延遲,因爲RunLoop沒有看到任何FSEvent,所以會阻止到 間隔的末尾。
這兩個特性都表明我誤解了一些非常基本的東西,大概是關於對象的生命週期。我想我可以 狀態以下每一項:
- 我確實希望調用
NSRunLoop runMode:beforeDate
程序的 主線程(程序沒有別的 做在等待,因此被阻止是正確的 事情)。這與 的Threading Programming Guide中RunLoops的解釋兼容。 - 每個線程只有一個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調用的內置計時器。 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。
(這是一個長期的問題:對不起通常的時候你問一個問題 這麼久,你已經制定了自己回答,但 - 沒了 - 我現在的困惑,因爲我以前。 )
謝謝,肯。我認爲我排除了來自其他框架的活動,理由是,如果發生這種活動,那麼我的回調就會聽到。但是,這種回調是由FSEvents事件流調用的真實情況,然後(單獨)將事件傳遞給RunLoop,RunLoop除了允許RunLoop退出之外什麼都不做。然而[FSEvents文檔](https://developer.apple.com/library/mac/#documentation/Darwin/Reference/FSEvents_Ref/Reference/reference.html)似乎表示回調是從「runloop」中收到的。這可能沒有關係,但是... – 2013-04-23 13:09:31
再次...我修改了我的代碼,以便它在默認模式下創建並計劃一個計時器。不過,我仍然不確定,究竟是誰/何地/什麼在發射我的回調,但我會暫時放下。按照您描述的方式共享運行循環是我很長一段時間內從NSRunLoop文檔中無法獲得的關鍵信息(我傾向於向Apple bugparade報告文檔錯誤) 。有趣的是,NSRunLoop的關鍵特性是它不是一個循環(它在一個循環中被調用),它不會運行(它會阻塞)。感謝指針。 – 2013-04-23 20:41:49
在Apple bugparade中作爲問題13719849添加(不是Cupertino以外的任何人都可以看到...) – 2013-04-23 21:00:16