Всем привет!
Недавно я работал над проектом, в котором по определенным причинам не мог использовать AFNetworking. При этом пользователь должен был знать о том, что в такой-то момент времени выполняется сетевой запрос. Для этого, помимо различных индикаторов внутри приложения, желательно управлять состоянием network activity indicator.

[UIApplication sharedApplication].networkActivityIndicatorVisible = YES; // NO

Напрямую этот код можно использовать в том случае, если есть гарантия, что одновременно выполняется только один запрос. В начале операции включаем индикатор, а после получение ответа — выключаем его. Но при количество запросов >1 этот код не будет работать корректно, так как индикатор выключится после завершения первого запроса.


Решить эту проблему можно, используя счетчик. При изменении количества запросов с 0 на 1 — индикатор включается, с 1 на 0 — выключается. Доступ к переменной-счетчику необходимо синхронизировать, что можно проделать по разному. Например, в AFNetworking используется директива @synchronized, кто-то использует метод OSAtomicAdd32 с параметрами 1 и -1, и т.д.

Проблема решена, однако на горизонте виднеется другая. Если протестировать этот код, то станет сразу заметно мерцание индикатора, которое появляется из-за многократного перехода состояния из включенного в выключенное и наоборот.
Чтобы избежать мерцания, требуется выполнять отключение индикатора с задержкой — в период ожидания количество запросов может быть опять увеличено и индикатор не будет выключен.
Очевидное решение — это таймер. В AFNetworking так и происходит — на главном потоке в NSRunLoop добавляется таймер, отвечающий за задержку:

- (void)updateNetworkActivityIndicatorVisibilityDelayed {
    if (self.enabled) {
        // Delay hiding of activity indicator for a short interval, to avoid flickering
        if (![self isNetworkActivityIndicatorVisible]) {
            [self.activityIndicatorVisibilityTimer invalidate];
            self.activityIndicatorVisibilityTimer = [NSTimer timerWithTimeInterval:kAFNetworkActivityIndicatorInvisibilityDelay target:self selector:@selector(updateNetworkActivityIndicatorVisibility) userInfo:nil repeats:NO];
            [[NSRunLoop mainRunLoop] addTimer:self.activityIndicatorVisibilityTimer forMode:NSRunLoopCommonModes];
        } else {
            [self performSelectorOnMainThread:@selector(updateNetworkActivityIndicatorVisibility) withObject:nil waitUntilDone:NO modes:@[NSRunLoopCommonModes]];
        }
    }
}


Однако несмотря на все вышеописанные способы, управлять состоянием сетевого индикатора можно еще проще.

Все знают про GCD. Многие советуют использовать именно очереди для реализации многопоточного доступа. Известно, что GCD позволяет создавать очереди двух тип — serial и concurrent. Для решения нашей задачи — включения/выключения сетевого индикатора идеально подходит первый тип.

Во-первых, при использовании сериальной очереди отпадает необходимость синхронизации доступа к счетчику (все задачи выполняются последовательно).

Во-вторых, никто не мешает нам использовать dispatch_after на сериальной очереди (правда, это надо делать осторожно).

В-третьих, необходимую нам сериальную очередь даже создавать не нужно. Она уже существует — это главная очередь, отвечающая за интерфейсные операции (main queue).

Согласно этим трем пунктам, привожу пример кода:

static int32_t requestsCount = 0;

+ (void)increment
{
    dispatch_async(dispatch_get_main_queue(), ^{
        if (requestsCount == 0)
        {
            [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
        }
        
        requestsCount++;
    });
}

+ (void)decrement
{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        if (requestsCount == 0)
        {
            return;
        }
        
        requestsCount--;
        
        if (requestsCount == 0)
        {
            [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
        }
    });
}


Методы increment и decrement являются методами класса (NetworkIndicatorManager). Но также можно сделать их методами экземпляра и оформить в виде категории для UIApplication.

Всем спасибо, всем добра!

Комментарии (0)