Я использую HanekeSwift для извлечения кэшированных данных, а затем устанавливаю для них метки в swipeView каждый раз, когда появляется представление. Мой код без проблем извлекает данные, но поскольку cache.fetch() асинхронный, когда я вызываю свой метод для обновления представления, мои ярлыки устанавливаются на nil. Есть ли способ сказать Swift подождать, пока мои кэшированные данные не будут извлечены, прежде чем загружать представление?

Смотрите код ниже:

override func viewWillAppear(animated: Bool) {
    updateEntries() // updates entries from cache when view appears
}

func updateEntries() {
    guard let accessToken = NSUserDefaults.standardUserDefaults().valueForKey("accessToken") as? String else { return }
    guard let cachedEntryKey = String(accessToken) + "food_entries.get" as? String else { return }
    cache.fetch(key: cachedEntryKey).onSuccess { data in
        ...
        // if successful, set labels in swipeView to data retrieved from cache
        ...
        dispatch_group_leave(dispatchGroup)
    } .onFailure { error in
        print(error)
        ...
        // if unsuccessful, call servers to retrieve data, set labels in swipeView to that data
        ...
        dispatch_group_leave(dispatchGroup)
    }
}

Когда я просматриваю приведенный выше код, он всегда отображает представление, а затем переходит в блок кеша. Как сделать так, чтобы viewWillAppear () разрешил updateEntries () завершиться и не возвращался из него, пока блок кеша не будет выполнен? Заранее спасибо!

Обновление 1:

Приведенное ниже решение работает довольно хорошо, и мои вызовы выполняются в правильной последовательности (мой оператор печати в блоке уведомлений выполняется после извлечения кеша), но мои представления обновляют свои метки только с ненулевыми значениями при вызове сервера. Может я ошибся код в группе уведомлений?

override func viewWillAppear(animated: Bool) {
    self.addProgressHUD()
    updateEntries() // updates entries from cache when view appears
}

func updateEntries() {
    guard let accessToken = NSUserDefaults.standardUserDefaults().valueForKey("accessToken") as? String else { return }
    guard let cachedEntryKey = String(accessToken) + "food_entries.get" as? String else { return }

    let dispatchGroup = dispatch_group_create()
    dispatch_group_enter(dispatchGroup)

    dispatch_group_async(dispatchGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
        cache.fetch(key: cachedEntryKey).onSuccess { data in
            ...
            // if successful, set labels in swipeView to data retrieved from cache
            ...
        } .onFailure { error in
            print(error)
            ...
            // if unsuccessful, call servers to retrieve data, set labels in swipeView to that data
            ...
        }
    }

    dispatch_group_notify(dispatchGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
        print("Retrieved Data")
        self.removeProgressHUD()
    }

}

Обновление 2:

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

«Это приложение изменяет механизм автоматического раскладки из фонового потока, что может привести к повреждению механизма и странным сбоям. Это вызовет исключение в следующем выпуске».

2
Obi Anachebe 5 Сен 2016 в 04:59

3 ответа

Хотя я полностью согласен с @ozgur относительно отображения какого-то индикатора загрузки с точки зрения UX, я понял, что полезно научиться использовать Grand Central Dispatch (собственное решение Apple для асинхронного ожидания) может помочь вам в долгосрочной перспективе.


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

Из документации Apple:

Группа диспетчеризации - это механизм мониторинга набора блоков. Ваше приложение может отслеживать блоки в группе синхронно или асинхронно в зависимости от ваших потребностей. В качестве расширения группа может быть полезна для синхронизации кода, который зависит от завершения других задач.

[...]

Группа отправки отслеживает количество ожидающих блоков, а GCD сохраняет группу до тех пор, пока все связанные с ней блоки не завершат выполнение.

Вот пример dispatch_groups в действии:

let dispatchGroup = dispatch_group_create()

dispatch_group_async(dispatchGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
    // Run whatever code you need to in here. It will only move to the final
    // dispatch_group_notify block once it reaches the end of the block.
}

dispatch_group_notify(dispatchGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
    // Code in here only runs once all dispatch_group_async blocks associated
    // with the dispatchGroup have finished completely.
}

Самое замечательное в dispatch_groups то, что они позволяют вам запускать несколько асинхронных блоков одновременно и ждать, пока все они закончатся, прежде чем запускать последний обработчик завершения. Другими словами, вы можете связать столько блоков dispatch_group_async с dispatchGroup, сколько хотите.

Если вы хотите использовать подход с индикатором загрузки (что вам и следовало бы), вы можете запустить код для отображения индикатора загрузки, а затем перейти к dispatch_group с обработчиком завершения, чтобы удалить индикатор загрузки и загрузить данные в представление один раз dispatch_group завершается.

0
Zachary Espiritu 5 Сен 2016 в 04:08

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

let alert = UIAlertController(title: "", message: "please wait ...", preferredStyle: .alert)

override func viewWillAppear(animated: Bool) {
    self.present(alert, animated: true, completion: nil)
    updateEntries() // updates entries from cache when view appears
}

func updateEntries() {
    guard let accessToken = UserDefaults.standard.value(forKey: "accessToken") as? String,
          let cachedEntryKey = (accessToken + "food_entries.get") as? String else { 
      return 
    }
    cache.fetch(key: cachedEntryKey).onSuccess { data in
        ...
             // update value in your UI
             alert.dismiss(animated: true, completion: nil)
        ...
        } .onFailure { error in
            print(error)
            ...
                // if unsuccessful, call servers to retrieve data, set labels in swipeView to that data
            ...
    }
}
0
Willjay 5 Сен 2016 в 03:45

Хорошо, предложения от всех очень помогли в этом. Думаю, я понял. Мне нужно убедиться, что мой блок кеша не блокирует основную очередь. См. Код ниже

ИЗМЕНИТЬ

Спасибо @Rob за то, что помог мне внести необходимые изменения, чтобы эта работа работала.

let dispatchGroup = dispatch_group_create()
dispatch_group_enter(dispatchGroup)

cache.fetch(key: cachedEntryKey).onSuccess { data in
    ...
    // if successful, set labels in swipeView to data retrieved from cache
    ...
    dispatch_group_leave(dispatchGroup)
} .onFailure { error in
    print(error)
    ...
    // if unsuccessful, call servers to retrieve data, set labels in swipeView to that data
    ...
    dispatch_group_leave(dispatchGroup)
}

dispatch_group_notify(dispatchGroup, dispatch_get_main_queue()) {
    print("Retrieved Data")
    self.removeProgressHUD()
}
1
Obi Anachebe 5 Сен 2016 в 18:54