2013-04-22 17 views
4

我試圖使用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。

(這是一個長期的問題:對不起通常的時候你問一個問題 這麼久,你已經制定了自己回答,但 - 沒了 - 我現在的困惑,因爲我以前。 )

回答

3

運行循環是一個共享資源。框架可以並且確實在運行循環中調度它們自己的運行循環源,特別是在默認模式下。如果-runMode:beforeDate:正在返回YES並且它沒有處理您的某個來源,那麼它可能會處理由框架調度的來源之一。

如果你想運行一個運行循環的方式只有只有你的源和定時器將觸發,那麼你需要安排你的源和定時器在自定義模式,並運行該模式下的運行循環。一種模式實際上只是一個字符串,所以使用類似@"com.yourcompany.yourproduct.yourmodename"或類似的保證是唯一的,你會沒事的。

或者您可以簡單地編寫代碼來處理並非所有在運行循環中觸發的源都是您的代碼的事實。如果要檢測超時到期,請安排計時器設置標誌並停止運行循環。保持循環,直到標誌被設置。我認爲您可以使用CFRunLoopStop()從您的計時器方法強制-runMode:beforeDate:返回,但如果不是,您可以使用-performSelector...方法之一,要麼採取線程或延遲來做到這一點。

+0

謝謝,肯。我認爲我排除了來自其他框架的活動,理由是,如果發生這種活動,那麼我的回調就會聽到。但是,這種回調是由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

+0

再次...我修改了我的代碼,以便它在默認模式下創建並計劃一個計時器。不過,我仍然不確定,究竟是誰/何地/什麼在發射我的回調,但我會暫時放下。按照您描述的方式共享運行循環是我很長一段時間內從NSRunLoop文檔中無法獲得的關鍵信息(我傾向於向Apple bugparade報告文檔錯誤) 。有趣的是,NSRunLoop的關鍵特性是它不是一個循環(它在一個循環中被調用),它不會運行(它會阻塞)。感謝指針。 – 2013-04-23 20:41:49

+0

在Apple bugparade中作爲問題13719849添加(不是Cupertino以外的任何人都可以看到...) – 2013-04-23 21:00:16

相關問題