Swift:在视图出现之前从异步调用中检索值

时间:2016-09-05 01:59:46

标签: swift asynchronous closures grand-central-dispatch haneke

我正在使用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()完成,并且在执行缓存块之前不返回它?非常感谢![/ p>

更新1:

下面的解决方案工作得很好,我的调用是按照正确的顺序进行的(我在通知块中的print语句在缓存检索后执行),但我的视图只在服务器是的时使用非零值更新标签调用。也许我在通知组中混淆了错误的代码?

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:

另外,当我切换视图时,我在控制台中收到此警告。我想我用上面的代码锁定主线程

“此应用程序正在从后台线程修改自动布局引擎,这可能导致引擎损坏和奇怪的崩溃。这将在将来的版本中导致异常。”

4 个答案:

答案 0 :(得分:1)

好的建议大家对此有所帮助。以为我明白了。我需要确保我的缓存块不阻塞主队列。见下面的代码

修改

感谢@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 :(得分:1)

注意:

  • 在调用异步方法之前输入group
  • 离开组是各自的完成/失败处理程序
  • 将通知块中的UI更新分派到队列

因此:

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 group = dispatch_group_create()
    dispatch_group_enter(group)

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

    dispatch_group_notify(group, dispatch_get_main_queue()) {
        print("Retrieved Data")
        self.removeProgressHUD()
    }

}

答案 2 :(得分:0)

以下是您可以暂存加载屏幕的简单示例。我只是创建一个提醒视图,您也可以创建自定义加载指标视图

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

答案 3 :(得分:0)

虽然我完全赞同@ozgur关于从UX角度展示某种加载指示器,但我认为学习如何使用Grand Central Dispatch(Apple的异步等待的原生解决方案)的好处可能会有所帮助你是长期的。

在运行某种类型的完成处理程序之前,您可以使用dispatch_groups等待代码块完全完成运行。

来自Apple's documentation

  

调度组是一种监视一组块的机制。您的应用程序可以根据您的需要同步或异步监视组中的块。通过扩展,一个组可以用于同步代码,这取决于其他任务的完成。

     

[...]

     

调度组会跟踪未完成的块数,并且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完成后进入视图。