我有一个UITableViewController,必须在启动时加载不太大量的数据。在我的viewDidLoad中,我使用其他队列发送请求:
override func viewDidLoad() {
super.viewDidLoad()
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
var data = self.getStoresData()
dispatch_async(dispatch_get_main_queue(), {
self.parseStoresData(data)
self.resultsController = PartnerStore.getAllStores()
});
});
}
这些是方法:
func getStoresData() -> [NSDictionary]
{
var responseData = [NSDictionary]()
self.refreshControl.beginRefreshing()
AppDelegate.appDelegate().httpRequestOperationManager?.GET(
"partner_stores/",
parameters: nil,
success: { (operation: AFHTTPRequestOperation!, responseObject: AnyObject!) in
self.tableView.reloadData();
println("RESPONSE OBJECT IN GET PARTNER STORES: \(responseObject)") },
failure: { (operation: AFHTTPRequestOperation!, error: NSError!) in
println("FAIL IN GET PARTNER STORES: \(error)") })
self.refreshControl.endRefreshing()
return responseData
}
func parseStoresData(storesData: [NSDictionary])
{
for storeDict in storesData {
// just inserts a new object to CoreData
PartnerStore.addNewStore(storeDict)
}
}
问题是(我认为)API调用是异步的,因此在从服务器下载数据之前,将执行dispatch_async中的两个函数。但是,如果我将所有内容放入GET调用的成功块中,则将花费大量时间,整个UI都将被阻塞。在不阻塞UI线程的情况下进行服务器调用的最佳方法是什么?
最佳答案
您对在dispatch_async
请求完成之前被调用的viewDidLoad
中GET
中的两个调用是正确的。那是个问题。当您说您尝试将所有内容放入success
块时,您也有一个正确的想法。那才是明智的选择。不过,还有一些其他事项需要移动。
处理UI更新的一种好方法是为UI更新和数据获取提供单独的功能。这样做意味着我们需要将回调传递给您的getStoresData
函数,然后在您的GET
函数的success
和error
块中适当地调用它。这将让我们知道何时完成数据提取,以便我们可以完成UI更新。我们还希望将调度从viewDidLoad
移到getStoresData
到后台队列。
因此,让我们从getStoresData
中拉出任何UI更改并移动该调度:
func getStoresData(callback: (error: NSError?) -> Void) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
// ... do any setup ...
AppDelegate.appDelegate().httpRequestOperationManager?.GET(
// ... other GET parameters ...
success: { (operation: AFHTTPRequestOperation!, responseObject: AnyObject!) in
var responseData = [NSDictionary]()
// do what you need to convert responseObject to responseData
// then...
// NOTE: we'll dispatch the the main thread here because parseStoresData deals with CoreData.
// This dispatch could be done in parseStoresData itself but
// a callback function would need to be added to it as well
// in that case.
dispatch_async(dispatch_get_main_queue(), {
self.parseStoresData(responseData)
// The response has been dealt with, so call the callback
callback(error: nil)
});
},
failure: { (operation: AFHTTPRequestOperation!, error: NSError!) in
// There was an error, so call the callback with the error object
callback(error: error)
}
)
})
}
现在,让我们使用新功能来处理UI更新,以便将数据更新与UI更新分离。在此功能中,我们将首先启动刷新控件并调用
getStoresData
。然后,当getStoresData
完成时,更新表视图并停止刷新控件。func reloadData() {
// start the refresh control on the main thread so the user knows we're updating
dispatch_async(dispatch_get_main_queue(), {
self.refreshControl.beginRefreshing()
})
// do the actual data fetch...
// (remember this will dispatch to a background thread on its own now)
getStoresData({
(error: NSError?) -> Void in
// since this callback could be called from any thread,
// make sure to dispatch back to the main UI thread to finish the UI updates
dispatch_async(dispatch_get_main_queue(), {
if let actualError = error {
// update the UI appropriately for the error
} else {
// update the data in the table view and reload it
self.resultsController = PartnerStore.getAllStores()
self.tableView.reloadData();
}
// we're done; stop the refresh control
self.refreshControl.endRefreshing()
})
})
}
这使您的
viewDidLoad
函数现在非常简单:override func viewDidLoad() {
super.viewDidLoad()
reloadData()
}
这也使实现拉动刷新等操作变得更加容易,因为您可以在用户触发刷新时简单地调用
reloadData
,而不必在整个位置复制UI更新代码。