我有一个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线程的情况下,服务器调用的最佳方法是什么?
答案 0 :(得分:1)
在dispatch_async
请求完成之前,viewDidLoad
GET
中的两个来电是正确的。这是一个问题。当你说你试图把所有东西放在success
区块时,你也有正确的想法;应该顺其自然的地方。还有一些其他的事情需要移动。
处理UI更新的一个好方法是为UI更新和数据提取分别提供功能。这样做意味着我们需要将回调传递给您的getStoresData
函数,然后在GET
函数success
和{{1}中正确调用它块。这将告诉我们数据提取何时完成,以便我们可以完成UI更新。我们还希望将调度移至error
之外的后台队列,然后移至viewDidLoad
。
因此,我们可以从getStoresData
中提取任何UI更改并移动该调度:
getStoresData
现在让我们使用新功能来处理更新UI,以便将数据更新与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)
}
)
})
}
。然后,当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()
}
,而不是在整个地方复制UI更新代码。