2013-06-06 2 views
4

Я только что прочитал следующее сообщение и попытались реализовать описанный подход здесь:Можно ли проверить, что основная нить простаивает/сливает основной цикл?

Writing iOS acceptance tests using Kiwi - Being Agile

Все вещи описаны там работает отлично. Но! есть одна вещь, которая ломает детерминизм, когда я выполняю приемочные испытания.

Вот репо на Github, где автор поста сдвинул экспериментов (его можно найти в нижней части страницы, в комментариях): https://github.com/moredip/2012-Olympics-iOS--iPad-and-iPhone--source-code/tree/kiwi-acceptance-mk1

Рассмотрим этот код, он использует для нарезания вид:

- (void) tapViewViaSelector:(NSString *)viewSelector{ 
    [UIAutomationBridge tapView:[self viewViaSelector:viewSelector]]; 
    sleepFor(0.1); //ugh 
} 

... где sleepFor имеет following definition behind itself:

#define sleepFor(interval) (CFRunLoopRunInMode(kCFRunLoopDefaultMode, interval, false)) 

это наивная попытка ('наивный' не абы но о том, что это первое, что приходит в голову) дождаться крошечного периода времени до тех пор, пока все анимации не обработаны и не замораживают все возможные события, которые (или могут быть) запланированы на (см. также this comment).

Проблема в том, что этот наивный код не работает детерминированным образом. Есть пучки взаимодействий пользовательского интерфейса, которые вызывают нажатие кнопки fx next button до исчезновения клавиатуры текущего отредактированного текстового поля и т. Д.

Если я просто увеличиваю время от 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(); 
} 

ответ

2

вы можете попробовать это

while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) == kCFRunLoopRunHandledSource); 

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

+0

спасибо за ответ. Это было мое оригинальное решение, прежде чем я создал этот пост. Я отправлю свой текущий через минуту. Я очень рад, что мы думаем в одном направлении! –

+0

Я обновил свой вопрос с первой попытки, чтобы вы могли поймать мое первоначальное мышление и способ его развития (надеюсь, что так)./cc @xlc –

1

Вот мое текущее решение, я добавлю некоторые комментарии и пояснения к коду немного позже, если никто не говорит мне, что я неправильно или предлагает лучший ответ первый:

// 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(); 
} 

Прямо сейчас я не» есть идеи, как это можно было бы сделать более эффективными.

+0

Я не вижу преимущества 'dispatch_async', и вы должны попытаться сделать это как цикл while вместо использования рекурсии. –

+0

Не могли бы вы обновить свой ответ с подробностями? –

+0

Преимущество dispatch_async заключается в том, чтобы гарантировать, что все блоки, отправленные в основную очередь __before__ runLoopIfNeeded() были вызваны, сначала сбрасываются. Неужели я ошибаюсь в этом рассуждении? –

3

Чтобы проверить состояние цикла запуска, связанного с потоком, и зарегистрировать обратные вызовы для отдельных фаз, вы можете использовать CFRunLoopObserverRef. Это позволяет осуществлять чрезвычайно мелкозернистый контроль при вызове обратных вызовов. Кроме того, вам не нужно зависеть от хакерских тайм-аутов и т. Д.

можно добавить как так (заметьте я добавляю один в основной цикл выполнения)

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; 
    } 
};