问题
I'm loading my UITableView from an Api call but although the data is retrieved fairly quickly, there is a significant time delay before it is loaded into the table. The code used is below
import UIKit
class TrackingInfoController: UIViewController, UITableViewDelegate, UITableViewDataSource {
@IBOutlet var table : UITableView?
@IBOutlet var indicator : UIActivityIndicatorView?
@IBOutlet var spinnerView : UIView?
var tableArrayList = Array<TableData>()
struct TableData
{
var dateStr:String = ""
var nameStr:String = ""
var codeStr:String = ""
var regionStr:String = ""
init(){}
}
override func viewDidLoad() {
super.viewDidLoad()
table!.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
spinnerView?.hidden = false
indicator?.bringSubviewToFront(spinnerView!)
indicator!.startAnimating()
downloadIncidents()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
@IBAction func BackToMain() {
performSegueWithIdentifier("SearchToMainSegue", sender: nil)
}
//#pragma mark - Table view data source
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1 //BreakPoint 2
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableArrayList.count;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomCell") as! CustomTableViewCell
cell.incidentDate.text = tableArrayList[indexPath.row].dateStr
cell.incidentText.text = tableArrayList[indexPath.row].nameStr
cell.incidentCode.text = tableArrayList[indexPath.row].codeStr
cell.incidentLoctn.text = tableArrayList[indexPath.row].regionStr
return cell //BreakPoint 4
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
AppDelegate.myGlobalVars.gIncName = tableArrayList[indexPath.row].nameStr
AppDelegate.myGlobalVars.gIncDMA = tableArrayList[indexPath.row].codeStr
performSegueWithIdentifier("SearchResultsToDetailSegue", sender: nil)
}
func alertView(msg: String) {
let dialog = UIAlertController(title: "Warning",
message: msg,
preferredStyle: UIAlertControllerStyle.Alert)
dialog.addAction(UIAlertAction(title: "Ok", style: .Default, handler: nil))
presentViewController(dialog,
animated: false,
completion: nil)
}
func downloadIncidents()
{
var event = AppDelegate.myGlobalVars.gIncName
var DMA = AppDelegate.myGlobalVars.gIncDMA
if event == "Enter Event Name" {
event = ""
}
if DMA == "Enter DMA" {
DMA = ""
}
let request = NSMutableURLRequest(URL: NSURL(string: "http://incident-tracker-api-uat.herokuapp.com/mobile/events?name=" + event)!,
cachePolicy: .UseProtocolCachePolicy,
timeoutInterval: 10.0)
request.HTTPMethod = "GET"
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
if error != nil {
self.alertView("Error - " + error!.localizedDescription)
}
else {
do {
var incidentList: TableData
if let json = try NSJSONSerialization.JSONObjectWithData(data!, options:.AllowFragments) as? Array<Dictionary<String, AnyObject>> {
for item in json {
if let dict = item as? Dictionary<String, AnyObject> {
incidentList = TableData()
if let nameStr = dict["name"] as? String {
incidentList.nameStr = nameStr
}
if let codeStr = dict["dma"] as? String {
incidentList.codeStr = codeStr
}
if let dateStr = dict["supplyOutageStart"] as? String {
let tmpStr = dateStr
let index = tmpStr.startIndex.advancedBy(10)
incidentList.dateStr = tmpStr.substringToIndex(index)
}
if let regionStr = dict["region"] as? String {
incidentList.regionStr = regionStr
}
self.tableArrayList.append(incidentList)
}
}
self.spinnerView?.hidden = true
self.indicator?.stopAnimating()
self.table?.reloadData() //BreakPoint 3
}
}catch let err as NSError
{
self.alertView("Error - " + err.localizedDescription)
}
}
})
task.resume() //BreakPoint 1
}
When the class is run, it hits BreakPoint 1 first and then hits BreakPoint 2 and then quickly goes to BreakPoint 3, it then goes to BreakPoint 2 once more. Then there is a delay of about 20 to 30 seconds before it hits Breakpoint 4 in cellForRowAtIndexPath() and the data is loaded into the UITableView. The view is displayed quickly afterwards.
The data is retrieved quite quickly from the Web Service so why is there a significant delay before the data is then loaded into the tableView? Is there a need to thread the Web Service method?
回答1:
You are getting server response in a background thread so you need to call the reloadData()
function on the UI thread. I am suspecting that the wait time can vary depending on whether you interact with the app, which effectively calls the UI thread, and that's when the table actually displays the new data.
In a nutshell, you need to wrap the self.table?.reloadData() //BreakPoint 3
with
dispatch_async(dispatch_get_main_queue()) {
// update some UI
}
The final result would be
Pre Swift 3.0
dispatch_async(dispatch_get_main_queue()) {
self.table?.reloadData()
}
Post Swift 3.0
DispatchQueue.main.async {
print("This is run on the main queue, after the previous code in outer block")
}
回答2:
The table view should begin to reload in a fraction of a second after you call tableView.reloadData()
.
If you make UI calls from a background thread, however, the results are "undefined". In practice, a common effect I've seen is for the UI changes to take an absurdly long time to actually take effect. The second most likely side-effect is a crash, but other, strange side-effects are also possible.
The completion handler for NSURLSession
calls is run on a background thread by default. You therefore need to wrap all your UI calls in a call to dispatch_async(dispatch_get_main_queue())
(which is now DispatchQueue.main.async()
in Swift 3.)
(If you are doing compute-intensive work like JSON parsing in your closure it's best to do that from the background so you don't block the main thread. Then make just the UI calls from the main thread.)
In your case you'd want to wrap the 3 lines of code marked with "breakpoint 3" (all UI calls) as well as the other calls to self.alertView()
Note that if you're sure the code in your completion closure is quick you can simply wrap the whole body of the closure in a call to dispatch_async(dispatch_get_main_queue())
.
回答3:
Just make sure you reload your tableview in inside the Dispatch main async, just immediately you get the data
来源:https://stackoverflow.com/questions/38744482/what-is-the-time-delay-between-getting-data-and-loading-to-uitableview