NSRunLoop: действительно ли он простаивает между kCFRunLoopBeforeWaiting и kCFRunLoopAfterWaiting?

Меня интересует цикл NSRunLoop, особенно для основного цикла выполнения. Через CFRunLoopObserverRef мы можем узнать о нем больше:

CFRunLoopObserverRef observerRef = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
    if (activity == kCFRunLoopBeforeTimers) {
        weakSelf.runloopId += 1;
    }
    NSLog(@"RunloopId : %lu, Activity : %ld, %lf\n", (unsigned long)weakSelf.runloopId, activity, [[NSDate date] timeIntervalSince1970]);
});
CFRunLoopAddObserver(CFRunLoopGetMain(), observerRef, kCFRunLoopCommonModes);

Мы можем игнорировать kCFRunLoopEntry и kCFRunLoopExit для основного цикла выполнения и сосредоточиться на kCFRunLoopBeforeTimers, kCFRunLoopBeforeSources, kCFRunLoopBeforeWaiting и kCFRunLoopAfterWaiting.

Согласно Последовательность событий цикла выполнения и CFRunLoop.c,

  1. один цикл основного цикла выполнения начинается с kCFRunLoopBeforeTimers,
  2. затем kCFRunLoopBeforeSources,
  3. после двух вышеупомянутых событий, если ничего не ожидается, иди спать,

поэтому, на мой взгляд, когда основной цикл переходит в спящий режим, он должен простаивать.

Обычное состояние ожидания

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

2016-03-08 21:58:33.522 CaptureJitterDemo[25272:1312010] RunloopId : 156, Activity : 2, 1457445513.522846
2016-03-08 21:58:33.523 CaptureJitterDemo[25272:1312010] RunloopId : 156, Activity : 4, 1457445513.522985
2016-03-08 21:58:33.523 CaptureJitterDemo[25272:1312010] RunloopId : 156, Activity : 32, 1457445513.523144
2016-03-08 21:58:49.002 CaptureJitterDemo[25272:1312010] RunloopId : 156, Activity : 64, 1457445529.002502
2016-03-08 21:58:49.002 CaptureJitterDemo[25272:1312010] RunloopId : 157, Activity : 2, 1457445529.002820
2016-03-08 21:58:49.003 CaptureJitterDemo[25272:1312010] RunloopId : 157, Activity : 4, 1457445529.003044

Activity : 32 означает beforeWaiting, а Activity : 64 означает afterWaiting, и между двумя состояниями основной стек вызовов выглядит следующим образом:

введите здесь описание изображения

Делать нечего, только ждать.

Удивительное состояние ожидания

Иногда я обнаруживаю, что что-то может произойти между beforeWaiting и afterWaiting:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    NSLog(@"begin of url request... %lf\n", [[NSDate date] timeIntervalSince1970]);
    NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://c2.staticflickr.com/4/3771/9914251535_366536a515_h.jpg"]];
    if (data) {
        NSLog(@"end of url request : %lf\n", [[NSDate date] timeIntervalSince1970]);
    }
}

когда я выбираю одну строку, консоль печатает:

2016-03-08 23:00:07.927 CaptureJitterDemo[25855:1334505] RunloopId : 27, Activity : 2, 1457449207.927184
2016-03-08 23:00:07.927 CaptureJitterDemo[25855:1334505] RunloopId : 27, Activity : 4, 1457449207.927399
2016-03-08 23:00:07.927 CaptureJitterDemo[25855:1334505] RunloopId : 27, Activity : 32, 1457449207.927624
2016-03-08 23:00:07.929 CaptureJitterDemo[25855:1334505] begin of url request... 1457449207.929021
2016-03-08 23:00:10.277 CaptureJitterDemo[25855:1334505] end of url request : 1457449210.276982
2016-03-08 23:00:10.278 CaptureJitterDemo[25855:1334505] RunloopId : 27, Activity : 64, 1457449210.278046
2016-03-08 23:00:10.278 CaptureJitterDemo[25855:1334505] RunloopId : 28, Activity : 2, 1457449210.278244
2016-03-08 23:00:10.278 CaptureJitterDemo[25855:1334505] RunloopId : 28, Activity : 4, 1457449210.278486
2016-03-08 23:00:10.278 CaptureJitterDemo[25855:1334505] RunloopId : 28, Activity : 32, 1457449210.278752

введите здесь описание изображения

Согласно журналам, между beforeWaiting и afterWaiting приложение выполнило одну задачу запроса URL. Это меня смущает.

Мое непонимание сначала

Сначала я подумал, что синхронный запрос URL в основном потоке приведет к тому, что основной цикл выполнения перейдет в состояние waiting. Но позже я обнаружил, что приложение сначала перешло в состояние waiting, а затем произошел запрос URL.

Итак, теперь это явление, когда основной цикл выполнения переходит в состояние waiting, он все еще может выполнять синхронный запрос URL-адреса, не просыпаясь — шутите?

Если я перемещу код на viewDidAppear:

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    
    NSLog(@"begin of url request... %lf\n", [[NSDate date] timeIntervalSince1970]);
    NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://c2.staticflickr.com/4/3771/9914251535_366536a515_h.jpg"]];
    if (data) {
        NSLog(@"end of url request : %lf\n", [[NSDate date] timeIntervalSince1970]);
    }
}

консоль печатает:

2016-03-08 23:42:25.616 CaptureJitterDemo[26268:1377354] RunloopId : 0, Activity : 1, 1457451745.616474
2016-03-08 23:42:25.617 CaptureJitterDemo[26268:1377354] RunloopId : 1, Activity : 2, 1457451745.617201
2016-03-08 23:42:25.617 CaptureJitterDemo[26268:1377354] RunloopId : 1, Activity : 4, 1457451745.617346
2016-03-08 23:42:25.618 CaptureJitterDemo[26268:1377354] begin of url request... 1457451745.618176
2016-03-08 23:42:27.832 CaptureJitterDemo[26268:1377354] end of url request : 1457451747.832428
2016-03-08 23:42:27.832 CaptureJitterDemo[26268:1377354] RunloopId : 2, Activity : 2, 1457451747.832818
2016-03-08 23:42:27.832 CaptureJitterDemo[26268:1377354] RunloopId : 2, Activity : 4, 

что кажется более приемлемым.

Что я действительно хочу сделать

Я хочу рассчитать время, затрачиваемое на цикл, в основном цикле выполнения, но время простоя не должно быть включено.

Цикл означает:

  1. Если сон не случился: kCFRunLoopBeforeTimers -> kCFRunLoopBeforeSources -> kCFRunLoopBeforeTimers;
  2. Если случился сон: kCFRunLoopBeforeTimers -> kCFRunLoopBeforeSources -> kCFRunLoopBeforeWaiting -> kCFRunLoopAfterWaiting -> kCFRunLoopBeforeTimers;

Для стоимости времени с kCFRunLoopBeforeWaiting по kCFRunLoopAfterWaiting:

  1. если это нормальное состояние ожидания, время следует исключить, потому что оно действительно простаивает;
  2. в противном случае, если это неожиданное состояние ожидания, время должно быть включено, потому что у него есть дела;

Спасибо за любую помощь :)


person Jason Lee    schedule 08.03.2016    source источник


Ответы (1)


Глядя на трассировку стека удивительное время ожидания, похоже, что сброс основной анимации происходит в результате наблюдения за циклом выполнения. Я предполагаю, что он наблюдает за активностью kCFRunLoopBeforeWaiting и в результате запускает didSelectRow... Поскольку вызов блокируется, цикл выполнения остается на этом шаге (шаг 6 в последовательности событий) до тех пор, пока семафор не подаст сигнал.

Чтобы добиться того, что вы хотите сделать - создайте отдельный наблюдатель для kCFRunLoopBeforeWaiting и kCFRunLoopAfterWaiting и в 4-м параметре (порядке) CFRunLoopObserverCreateWithHandler передайте в LONG_MAX и 0 соответственно.

Кроме того, лучше использовать CACurrentMediaTime() вместо [[NSDate date] timeIntervalSince1970], так как это монотонно.

person Krys Jurgowski    schedule 28.12.2016