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

Swift: Retrieve value from asynchronous call before view appears

我正在使用 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() 完成,而不是 return 在执行缓存块之前退出它?提前致谢!

更新 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()
    }

}

更新二:

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

"This application is modifying the autolayout engine from a background thread, which can lead to engine corruption and weird crashes. This will cause an exception in a future release."

这是一个简单的示例,您可以展示一个加载屏幕。我只是创建了一个警报视图,您也可以创建自定义 加载指示器视图

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

虽然我完全同意@ozgur 关于从用户体验的角度显示某种加载指示器的观点,但我认为学习如何使用 Grand Central Dispatch(Apple 的异步等待本地解决方案)的好处可能会帮助您长期。


您可以使用 dispatch_groups 等待代码块完全完成 运行ning,然后 运行ning 某种完成处理程序。

来自Apple's documentation

A dispatch group is a mechanism for monitoring a set of blocks. Your application can monitor the blocks in the group synchronously or asynchronously depending on your needs. By extension, a group can be useful for synchronizing for code that depends on the completion of other tasks.

[...]

The dispatch group keeps track of how many blocks are outstanding, and GCD retains the group until all its associated blocks complete execution.

下面是 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 完成后加载指示器和加载数据进入视图。

每个人的好的建议在这方面帮助很大。想我明白了。我需要确保我的缓存块没有阻塞主队列。请参阅下面的代码

编辑

感谢@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()
}

注意:

  • 调用异步方法前进群
  • 离开组是各自的 completion/failure 个处理程序
  • 调度UI通知块中的更新到main队列

因此:

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

}