Лучший способ заставить 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 секунды друг от друга.

Любые идеи, почему оригинальный код демонстрирует такое поведение?

Runloops может быть немного волшебным полем, где только что происходит.

В основном вы говорите runloop, чтобы обработать некоторые события, а затем вернуться. ИЛИ верните, если он не обработает какие-либо события до того, как истечет время ожидания.

С 0,1-секундным таймаутом вы чаще всего используете тайм-аут. Runloop fire, не обрабатывает никаких событий и не возвращается в 0,1 секунды. Иногда у него будет возможность обработать событие.

С тайм-аутом farFuture runloop будет ждать до тех пор, пока не обработает событие. Поэтому, когда он возвращается к вам, он только что обработал какое-то событие.

Короткое значение тайм-аута будет потреблять значительно больше ЦП, чем бесконечный тайм-аут, но есть веские причины для использования короткого тайм-аута, например, если вы хотите завершить процесс / stream, в котором работает runloop. Вероятно, вы захотите, чтобы runloop заметил что флаг изменился и что он должен выручить как можно скорее.

Возможно, вам захочется поиграть с наблюдателями, чтобы вы могли точно видеть, что делает runloop.

Дополнительную информацию см. В этом документе Apple .

Хорошо, я объяснил вам эту проблему, вот возможное решение:

 @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 

start отображает индикатор прогресса и фиксирует основной stream во внутренней runloop. Он останется там, пока другая нить не объявит, что это сделано. Чтобы разбудить основной stream, он заставит его обрабатывать функцию без каких-либо целей, кроме пробуждения основной нити.

Это один из способов, как вы можете это сделать. Уведомление, отправляемое и обрабатываемое в основном streamе, может быть предпочтительным (также для него могут зарегистрироваться другие streamи), но вышеприведенное решение является самым простым, о котором я могу думать. Кстати, это не очень streamобезопасно. Чтобы действительно быть streamобезопасным, каждый доступ к логическому объекту должен быть заблокирован объектом NSLock из любого streamа (использование такой блокировки также делает «изменчивым» устаревшим, поскольку переменные, защищенные блокировкой, являются неявными волатильными в соответствии с стандартом POSIX; Однако стандарт C не знает о блокировках, поэтому здесь только volatile может гарантировать, что этот код будет работать, GCC не нуждается в изменчивости для переменной, защищенной блокировками).

В общем, если вы сами обрабатываете события в цикле, вы делаете это неправильно. По моему опыту это может вызвать массу проблем.

Если вы хотите запустить модально – например, показывая панель прогресса – запустите модально! Идем дальше и используем методы 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

Если вы хотите установить переменную флага и сразу заметить цикл цикла, просто используйте -[NSRunLoop performSelector:target:argument:order:modes: запросить цикл выполнения для вызова метода, который устанавливает флаг в false. Это вызовет немедленное rotation цикла запуска, метод, который будет вызываться, и затем флаг будет проверен.

В вашем коде текущий stream будет проверять изменение переменной каждые 0,1 секунды. В примере с кодом Apple изменение переменной не будет иметь никакого эффекта. Runloop будет работать до тех пор, пока не обработает какое-либо событие. Если значение webViewIsLoading было изменено, событие не генерируется автоматически, поэтому оно останется в цикле, почему оно выйдет из него? Он останется там, пока не пройдет какое-то другое событие для обработки, а затем оно выйдет из него. Это может произойти в 1, 3, 5, 10 или даже 20 секунд. И до тех пор, пока это не произойдет, он не выйдет из runloop и, следовательно, не заметит, что эта переменная изменилась. IOW код Apple, который вы цитируете, является недетерминированным. Этот пример будет работать только в том случае, если изменение значения для webViewIsLoading также создает событие, которое заставляет runloop просыпаться, и это, похоже, не так (или, по крайней мере, не всегда).

Я думаю, вы должны переосмыслить проблему. Поскольку ваша переменная называется webViewIsLoading, вы ждете загрузки веб-страницы? Вы используете Webkit для этого? Я сомневаюсь, что вам нужна такая переменная вообще, ни какой-либо из кода, который вы опубликовали. Вместо этого вы должны асинхронно закодировать свое приложение. Вы должны запустить «процесс загрузки веб-страницы», а затем вернуться к основному циклу, и как только страница закончит загрузку, вы должны асинхронно отправить уведомление, которое обрабатывается в основном streamе, и запускает код, который должен запускаться, как только загрузка завершена.

У меня были подобные проблемы при попытке управлять NSRunLoops . Обсуждение для runMode:beforeDate: на странице ссылок на classы говорит:

Если к циклу запуска не подключены никакие входные источники или таймеры, этот метод немедленно прекращается; в противном случае он возвращается после обработки первого входного источника или достижения limitDate. Ручное удаление всех известных источников входного сигнала и таймеров из цикла запуска не гарантирует, что цикл выполнения будет завершен. Mac OS X может устанавливать и удалять дополнительные источники входных данных по мере необходимости для обработки запросов, направленных на stream получателя. Поэтому эти источники могут помешать запуску цикла.

Мое лучшее предположение заключается в том, что источник ввода подключен к вашему NSRunLoop , возможно, самой OS X, и этот runMode:beforeDate: блокируется до тех пор, пока этот источник ввода не будет обработан каким-либо входом или не будет удален. В вашем случае он принимал « пару секунд и до 10 секунд », чтобы это произошло, и в этот момент runMode:beforeDate: вернется с логическим значением, while() снова запустится, он обнаружит, что shouldKeepRunning был установлен к NO , и цикл завершится.

С вашей доработкой runMode:beforeDate: вернется в течение 0,1 секунды, независимо от того, подключили ли они входные источники или обработали какой-либо вход. Это образованная догадка (я не специалист по внутренним циклам цикла), но думаю, что ваша утонченность – правильный способ справиться с ситуацией.

Второй пример просто работает во время опроса, чтобы проверить ввод цикла запуска в интервале времени 0,1.

Иногда я нахожу решение для вашего первого примера:

 BOOL shouldKeepRunning = YES; // global NSRunLoop *theRL = [NSRunLoop currentRunLoop]; while (shouldKeepRunning && [theRL runMode:NSRunLoopCommonModes beforeDate:[NSDate distantFuture]]); 
  • Интеграция CFNetwork и Bonjour для интеграции iPhone и Mac
  • Как изменить определенный цвет в изображении?
  • Как resize изображения программно в объективе-c в iphone
  • Можете ли вы настраивать анимацию для встраиваемых ячеек UITableView?
  • Objective-c iPhone-процент кодирует строку?
  • Как прокрутить UITableView в определенную позицию
  • UIImage от CALayer - iPhone SDK
  • NSOperation на iPhone
  • UITextView линейчатый фон, но неправильная высота строки
  • Остановить UIWebView от «подпрыгивания» по вертикали?
  • Сериализация и десериализация объектов Objective-C в JSON
  • Давайте будем гением компьютера.