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 某种完成处理程序。
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()
}
}
我正在使用 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 某种完成处理程序。
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()
}
}