[英]Is it possible to check that main thread is idle / to drain a main run loop?
我剛剛閱讀了以下帖子,並嘗試實現此處描述的方法:
那里描述的所有東西都可以正常工作。 但! 當我運行驗收測試時,有一件事破壞了確定性。
這是Github上的回購,文章的作者在其中進行了他的實驗(可以在評論的頁面底部找到): https : //github.com/moredip/2012-Olympics-iOS--iPad-and -iPhone--源代碼/樹/獼猴桃接受-mk1
考慮一下他用於點擊視圖的以下代碼:
- (void) tapViewViaSelector:(NSString *)viewSelector{
[UIAutomationBridge tapView:[self viewViaSelector:viewSelector]];
sleepFor(0.1); //ugh
}
...其中sleepFor
具有以下定義 :
#define sleepFor(interval) (CFRunLoopRunInMode(kCFRunLoopDefaultMode, interval, false))
這是一個幼稚的嘗試(“幼稚”不是關於作者,而是關於它是進入頭腦的第一件事),要等待一小段時間,直到處理完所有動畫並吸收所有可能的時間為止。已(或可能)安排到主運行循環的事件(另請參見此評論 )。
問題在於,這種幼稚的代碼無法以確定性的方式工作。 大量的UI交互導致在當前編輯的文本字段的鍵盤消失之前按下FX的下一個按鈕,依次類推...
如果我將時間從0.1增加到fx 1,所有的問題都會消失,但這會導致每一次交互(例如“用文本填充文本字段...”或“點擊標題的點擊按鈕...”)都變成一個第二!
因此,我並不是要在這里增加等待時間,而是要進行這樣的人為等待,以確保我可以繼續下一步測試用例。
我希望這是一種等待時間足夠可靠的方法,直到由當前操作引起的所有工作(所有過渡/動畫或任何主運行循環工作)完成為止。
總而言之,這是一個問題:
有沒有一種方法可以用盡所有要排到主線程及其運行循環中的內容,以確保主線程處於空閑狀態並且其運行循環為“空”?
這是我最初的解決方案:
// DON'T like it
static inline void runLoopIfNeeded() {
// https://developer.apple.com/library/mac/#documentation/CoreFOundation/Reference/CFRunLoopRef/Reference/reference.html
while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, YES) == kCFRunLoopRunHandledSource);
// DON'T like it
if (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, YES) == kCFRunLoopRunHandledSource) runLoopIfNeeded();
}
你可以試試這個
while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) == kCFRunLoopRunHandledSource);
這將一直運行,直到運行循環中沒有更多東西為止。 如果0不起作用,您可以嘗試將時間間隔更改為0.1。
要檢查與線程關聯的運行循環的狀態並注冊單獨階段的回調,可以使用CFRunLoopObserverRef
。 這樣可以對調用回調的時間進行非常精細的控制。 另外,您不必依賴於hacky超時等。
可以這樣添加一個(注意,我要在主運行循環中添加一個)
CFRunLoopObserverRef obs = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, true, 0 /* order */, handler);
CFRunLoopAddObserver([NSRunLoop mainRunLoop].getCFRunLoop, obs, kCFRunLoopCommonModes);
CFRelease(obs);
根據您注冊的活動,您的處理程序將被適當調用。 在上面的示例中,觀察者監聽所有活動。 您可能只需要kCFRunLoopBeforeWaiting
您的處理程序可能看起來像這樣
id handler = ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
// About to enter the processing loop. Happens
// once per `CFRunLoopRun` or `CFRunLoopRunInMode` call
break;
case kCFRunLoopBeforeTimers:
case kCFRunLoopBeforeSources:
// Happens before timers or sources are about to be handled
break;
case kCFRunLoopBeforeWaiting:
// All timers and sources are handled and loop is about to go
// to sleep. This is most likely what you are looking for :)
break;
case kCFRunLoopAfterWaiting:
// About to process a timer or source
break;
case kCFRunLoopExit:
// The `CFRunLoopRun` or `CFRunLoopRunInMode` call is about to
// return
break;
}
};
這是我當前的解決方案,如果沒人告訴我我錯了或者建議先給出一個更好的答案,我將在代碼中添加一些注釋和解釋。
// It is much better, than it was, but still unsure
static inline void runLoopIfNeeded() {
// https://developer.apple.com/library/mac/#documentation/CoreFOundation/Reference/CFRunLoopRef/Reference/reference.html
__block BOOL flag = NO;
// http://stackoverflow.com/questions/7356820/specify-to-call-someting-when-main-thread-is-idle
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
flag = YES;
});
});
while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, YES) == kCFRunLoopRunHandledSource);
if (flag == NO) runLoopIfNeeded();
}
現在我還不知道如何使它更有效。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.