2008-09-29 78 views
39

NSRunLoop的Apple文檔中,有示例代碼演示暫停執行,同時等待某個標誌由其他設置。讓NSRunLoop等待標誌被設置的最佳方式?

BOOL shouldKeepRunning = YES;  // global 
NSRunLoop *theRL = [NSRunLoop currentRunLoop]; 
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]); 

我一直在使用這個,它的工作原理,但在調查性能問題,我跟蹤到這段代碼。我使用幾乎完全相同的代碼(只是國旗的名稱是不同的:),如果我在設置標誌(在另一種方法中)之後在線上放置NSLog,然後在while()之後有一行看似在幾秒鐘的兩個日誌語句之間隨機等待。

延遲在較慢或較快的機器上似乎並不不同,但從運行到運行至少有幾秒鐘和多達10秒不等。

我已經用下面的代碼解決了這個問題,但原始代碼不起作用似乎不正確。

NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1]; 
while (webViewIsLoading && [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate:loopUntil]) 
    loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1]; 

使用此代碼時,設置標誌和while循環後的日誌語句現在始終小於0.1秒。

任何人有任何想法,爲什麼原代碼表現出這種行爲?

+0

我想你是誤會文檔中給出的示例;這個示例代碼的用法是當你想終止一個runloop的執行時(不要得到關於標誌變化的通知)https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/ NSRunLoop_Class/Reference/Reference.html#// apple_ref/occ/instm/NSRunLoop/run – kernix 2014-04-18 20:06:40

回答

35

Runloops可以是一個魔術盒,只是發生的東西。

基本上你要告訴runloop去處理一些事件然後返回。如果在超時命中之前沒有處理任何事件,則返回。

在0.1秒的超時時間內,你經常會發現超時。 runloop觸發,不處理任何事件並在0.1秒內返回。偶爾會有機會處理一個事件。

隨着你遙遠的未來超時,runloop將等待,直到它處理一個事件。所以當它回到你身上時,它只是處理了某種事件。

短暫超時值會比無限超時消耗更多的CPU,但是使用短暫超時有很好的理由,例如,如果要終止runloop運行的進程/線程,您可能需要runloop注意到一個標誌已經改變,它需要儘快救助。

您可能想要與runloop觀察員一起玩,以便您可以看到runloop正在做什麼。

查看this Apple doc瞭解更多信息。

2

我在嘗試管理NSRunLoops時遇到過類似的問題。該discussionrunMode:beforeDate:類引用頁面上說:

如果沒有輸入源或定時器連接到運行循環,此方法立即退出;否則,在處理第一個輸入源或達到limitDate後返回。手動從運行循環中刪除所有已知的輸入源和定時器並不能保證運行循環將退出。 Mac OS X可以根據需要安裝和刪除額外的輸入源,以處理針對接收方線程的請求。這些來源因此可以防止運行循環退出。

我最好的猜測是,一個輸入源連接到您的NSRunLoop,也許OS X一樣,這runMode:beforeDate:阻止,直到輸入源或者有一定的輸入處理,或者被刪除。在你的情況下,它是「幾秒鐘,最多10秒」發生這種情況,此時runMode:beforeDate:將返回一個布爾值,while()將再次運行,它會檢測到shouldKeepRunning已被設置爲NO,並且循環會終止。

隨着您的改進,runMode:beforeDate:將在0.1秒內返回,無論它是否已連接輸入源或已處理任何輸入。這是一個有教養的猜測(我不是運行循環內部的專家),但認爲你的優化是處理這種情況的正確方法。

10

如果您希望能夠設置標誌變量並立即注意到運行循環,只需使用-[NSRunLoop performSelector:target:argument:order:modes:就可以讓運行循環調用將標誌設置爲false的方法。這將導致你的運行循環立即旋轉,被調用的方法,然後該標誌將被檢查。

+1

感謝Chris,不幸的是,由於該標誌是在WebView的委託方法中設置的,因此我不能立即調用它。 – 2008-10-07 05:11:05

5

在您的代碼中,當前線程將檢查每0.1秒更改一次的變量。在Apple代碼示例中,更改變量不會產生任何影響。 runloop將運行直到它處理一些事件。如果webViewIsLoading的值已更改,則不會自動生成任何事件,因此它將保留在循環中,爲什​​麼會跳出它?它會留在那裏,直到它得到一些其他的事件來處理,然後它會打破它。這可能發生在1,3,5,10甚至20秒內。在這種情況發生之前,它不會跳出runloop,因此它不會注意到這個變量已經改變。 IOW你引用的Apple代碼是不確定的。這個例子只有在webViewIsLoading的值更改也會創建一個導致runloop醒來的事件並且這似乎不是這種情況(或者至少並非總是)時才起作用。

我認爲你應該重新考慮這個問題。由於您的變量名爲webViewIsLoading,您是否等待加載網頁?你使用的是Webkit嗎?我懷疑你根本就不需要這樣的變量,也沒有你發佈的任何代碼。相反,你應該編寫你的應用程序異步。您應該啓動「網頁加載過程」,然後返回到主循環,並且一旦頁面完成加載,您應該異步發佈在主線程中處理的通知,並運行儘快運行的代碼加載已完成。

+1

這是一個很好的解釋,爲什麼RunLoop沒有爆發,謝謝。有一個很好的理由,爲什麼WebView加載需要模態,但我需要保持這樣。 – 2008-10-07 05:10:21

15

好吧,我解釋你的問題,這裏有一個可能的解決方案:

@implementation MyWindowController 

volatile BOOL pageStillLoading; 

- (void) runInBackground:(id)arg 
{ 
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 

    // Simmulate web page loading 
    sleep(5); 

    // This will not wake up the runloop on main thread! 
    pageStillLoading = NO; 

    // Wake up the main thread from the runloop 
    [self performSelectorOnMainThread:@selector(wakeUpMainThreadRunloop:) withObject:nil waitUntilDone:NO]; 

    [pool release]; 
} 


- (void) wakeUpMainThreadRunloop:(id)arg 
{ 
    // This method is executed on main thread! 
    // It doesn't need to do anything actually, just having it run will 
    // make sure the main thread stops running the runloop 
} 


- (IBAction)start:(id)sender 
{ 
    pageStillLoading = YES; 
    [NSThread detachNewThreadSelector:@selector(runInBackground:) toTarget:self withObject:nil]; 
    [progress setHidden:NO]; 
    while (pageStillLoading) { 
     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; 
    } 
    [progress setHidden:YES]; 
} 

@end 

開始顯示進度指示器和捕捉在內部runloop主線程。它會留在那裏,直到其他線程宣佈它完成。要喚醒主線程,除了喚醒主線程之外,它將使其無需處理任何功能。

這只是你如何做到的一種方式。在主線程上發佈和處理通知可能更可取(其他線程也可以註冊),但上述解決方案是我能想到的最簡單的解決方案。順便說一句,它並不是真的線程安全的。要真正成爲線程安全的,每個對布爾的訪問都需要被任一線程的NSLock對象鎖定(使用這樣的鎖也會使「volatile」過時,因爲受鎖保護的變量根據POSIX標準是隱式的volatile; C標準,但不知道鎖,所以在這裏只有volatile才能保證這段代碼正常工作; GCC不需要爲被lock保護的變量設置volatile。

+0

非常酷。這增加了一個新的工具,我objc武庫。考慮將您的技術添加到[此線程] [1] [1]:http://stackoverflow.com/questions/155964/what-are-best-practices-that-you-use-when-writing-objective- c-and-cocoa#156343 – schwa 2008-10-07 14:54:43

11

一般來說,如果你自己在一個循環中處理事件,你就是在做錯了。根據我的經驗,它可能會導致大量雜亂的問題。

如果你想模態運行 - 例如,顯示進度面板 - 模態運行!繼續使用NSApplication方法,以模態方式運行進度表,然後在加載完成後停止模式。請參閱Apple文檔,例如http://developer.apple.com/documentation/Cocoa/Conceptual/WinPanel/Concepts/UsingModalWindows.html

如果您只是希望視圖能夠在您的加載期間內運行,但您不希望它是模態的(例如,您希望其他視圖能夠響應事件),那麼您應該更簡單一些。例如,你可以這樣做:

- (IBAction)start:(id)sender 
{ 
    pageStillLoading = YES; 
    [NSThread detachNewThreadSelector:@selector(runInBackground:) toTarget:self withObject:nil]; 
    [progress setHidden:NO]; 
} 

- (void)wakeUpMainThreadRunloop:(id)arg 
{ 
    [progress setHidden:YES]; 
} 

你就完成了。無需保持運行循環的控制!

-Wil

0

你的第二個例子,是解決你輪詢的時間間隔內0.1檢查運行循環的輸入。

偶爾我找到你的第一個例子中的解決方案:

BOOL shouldKeepRunning = YES;  // global 
NSRunLoop *theRL = [NSRunLoop currentRunLoop]; 
while (shouldKeepRunning && [theRL runMode:NSRunLoopCommonModes beforeDate:[NSDate distantFuture]]); 
相關問題