如何在下载JSON时修复延迟UITableView滚动性能?

时间:2022-01-20 14:26:24

In my application, I download a JSON file off of the internet and fill up a UITableView with items from the file. It does work well, and there are no problems or errors, but the scrolling performance is very laggy, and the UI glitches out a tiny bit.

在我的应用程序中,我从Internet上下载了一个JSON文件,并用文件中的项填充UITableView。它确实运行良好,并且没有任何问题或错误,但滚动性能非常滞后,并且UI出现了一点点故障。

I assume this is because of the images that I'm downloading from the JSON file, so I've looked into multi-threading, but I don't think I am doing it right because it does load much faster, but scrolling performance is still the same as before.

我认为这是因为我正在从JSON文件下载的图像,所以我研究了多线程,但我不认为我做得对,因为它的加载速度要快得多,但滚动性能是仍然和以前一样。

Can somebody please tell me how to fix this? This UITableView is the most important thing in the app, and I have been spending much time on trying to fix it. Thank you!

有人可以告诉我如何解决这个问题吗?这个UITableView是应用程序中最重要的东西,我花了很多时间试图修复它。谢谢!

Here is my code-

这是我的代码 -

import UIKit

class ViewController: UIViewController, UITableViewDataSource {

@IBOutlet weak var tableView: UITableView!

var nameArray = [String]()
var idArray = [String]()
var ageArray = [String]()
var genderArray = [String]()
var descriptionArray = [String]()
var imgURLArray = [String]()

let myActivityIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.gray)

final let urlString = "https://pbsocfilestorage.000webhostapp.com/jsonDogs.json"

override func viewDidLoad() {
    super.viewDidLoad()

    self.downloadJsonWithURL()

    // Activity Indicator
    myActivityIndicator.center = view.center
    myActivityIndicator.hidesWhenStopped = true
    myActivityIndicator.startAnimating()
    view.addSubview(myActivityIndicator)

}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

func downloadJsonWithURL() {
    let url = NSURL(string:urlString)
    URLSession.shared.dataTask(with: (url as? URL)!, completionHandler: {(data, response, error) ->
        Void in
        print("Good so far...")
        if let jsonObj = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? NSDictionary {
            print(jsonObj!.value(forKey: "dogs"))

            if let dogArray = jsonObj!.value(forKey: "dogs") as? NSArray {
                print("Why u no work!")
                for dog in dogArray {

                    if let dogDict = dog as? NSDictionary {
                        if let name = dogDict.value(forKey: "name") {
                            self.nameArray.append(name as! String)
                        }
                        if let name = dogDict.value(forKey: "id") {
                            self.idArray.append(name as! String)
                        }
                        if let name = dogDict.value(forKey: "age") {
                            self.ageArray.append(name as! String)
                        }
                        if let name = dogDict.value(forKey: "gender") {
                            self.genderArray.append(name as! String)
                        }
                        if let name = dogDict.value(forKey: "image") {
                                self.imgURLArray.append(name as! String)
                        }
                        if let name = dogDict.value(forKey: "description") {
                            self.descriptionArray.append(name as! String)
                        }

                        OperationQueue.main.addOperation ({
                            self.myActivityIndicator.stopAnimating()
                            self.tableView.reloadData()
                        })

                    }
                }
            }
        }

    }).resume()
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return nameArray.count
}

func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return  UITableViewAutomaticDimension;
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let imgURL = NSURL(string: imgURLArray[indexPath.row])
    let cell = tableView.dequeueReusableCell(withIdentifier: "reusableCell") as! TableViewCell

    URLSession.shared.dataTask(with: (imgURL as! URL), completionHandler: {(data, resp, error) -> Void in

        if (error == nil && data != nil) {
            OperationQueue.main.addOperation({
                cell.dogNameLabel.text = self.nameArray[indexPath.row]
                cell.idLabel.text = self.idArray[indexPath.row]
                cell.ageLabel.text = self.ageArray[indexPath.row]
                cell.genderLabel.text = self.genderArray[indexPath.row]
                print("Cell info was filled in!")

                if imgURL != nil {
                    let data = NSData(contentsOf: (imgURL as? URL)!)
                    cell.dogImage.image = UIImage(data: data as! Data)
                }
            })
        }
    }).resume()

    return cell
}

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "showDog" {
        if let indexPath = self.tableView.indexPathForSelectedRow{
            let detailViewController = segue.destination as! DetailViewController
            detailViewController.imageString = imgURLArray[indexPath.row]
            detailViewController.nameString = nameArray[indexPath.row]
            detailViewController.idString = idArray[indexPath.row]
            detailViewController.ageString = ageArray[indexPath.row]
            detailViewController.descriptionString = descriptionArray[indexPath.row]
            detailViewController.genderString = genderArray[indexPath.row]
        }
    }
}
}

1 个解决方案

#1


1  

There is a big mistake. You are loading data with dataTask but you aren't using that returned data at all. Rather than you are loading the data a second time with synchronous contentsOf. Don't do that.

有一个很大的错误。您正在使用dataTask加载数据,但您根本没有使用返回的数据。而不是您使用同步contentsOf第二次加载数据。不要那样做。

And don't update the labels in the asynchronous completion block. The strings are not related to the image data.

并且不要更新异步完成块中的标签。字符串与图像数据无关。

This is more efficient:

这更有效:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let imgURL = URL(string: imgURLArray[indexPath.row])
    let cell = tableView.dequeueReusableCell(withIdentifier: "reusableCell", for: indexPath) as! TableViewCell

    cell.dogNameLabel.text = self.nameArray[indexPath.row]
    cell.idLabel.text = self.idArray[indexPath.row]
    cell.ageLabel.text = self.ageArray[indexPath.row]
    cell.genderLabel.text = self.genderArray[indexPath.row]
    print("Cell info was filled in!")

    URLSession.shared.dataTask(with: imgURL!) { (data, resp, error) in

        if let data = data {
            OperationQueue.main.addOperation({
                cell.dogImage.image = UIImage(data: data)
            })
        }
    }.resume()

    return cell
}

Note: You are strongly discouraged from using multiple arrays as data source. It's very error-prone. Use a custom struct or class. And create imgURLArray with URL instances rather than strings. This is also much more efficient.

注意:强烈建议您不要将多个数组用作数据源。这很容易出错。使用自定义结构或类。并使用URL实例而不是字符串创建imgURLArray。这也更有效率。

Nevertheless you should use a download manager which caches the images and cancels downloads if a cell goes off-screen. At the moment each image is downloaded again when the user scrolls and cellFoRow is called again for this particular cell.

不过你应该使用一个下载管理器来缓存图像,如果一个单元离开屏幕就取消下载。此时,当用户滚动时再次下载每个图像,并再次为该特定单元调用cellFoRow。

#1


1  

There is a big mistake. You are loading data with dataTask but you aren't using that returned data at all. Rather than you are loading the data a second time with synchronous contentsOf. Don't do that.

有一个很大的错误。您正在使用dataTask加载数据,但您根本没有使用返回的数据。而不是您使用同步contentsOf第二次加载数据。不要那样做。

And don't update the labels in the asynchronous completion block. The strings are not related to the image data.

并且不要更新异步完成块中的标签。字符串与图像数据无关。

This is more efficient:

这更有效:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let imgURL = URL(string: imgURLArray[indexPath.row])
    let cell = tableView.dequeueReusableCell(withIdentifier: "reusableCell", for: indexPath) as! TableViewCell

    cell.dogNameLabel.text = self.nameArray[indexPath.row]
    cell.idLabel.text = self.idArray[indexPath.row]
    cell.ageLabel.text = self.ageArray[indexPath.row]
    cell.genderLabel.text = self.genderArray[indexPath.row]
    print("Cell info was filled in!")

    URLSession.shared.dataTask(with: imgURL!) { (data, resp, error) in

        if let data = data {
            OperationQueue.main.addOperation({
                cell.dogImage.image = UIImage(data: data)
            })
        }
    }.resume()

    return cell
}

Note: You are strongly discouraged from using multiple arrays as data source. It's very error-prone. Use a custom struct or class. And create imgURLArray with URL instances rather than strings. This is also much more efficient.

注意:强烈建议您不要将多个数组用作数据源。这很容易出错。使用自定义结构或类。并使用URL实例而不是字符串创建imgURLArray。这也更有效率。

Nevertheless you should use a download manager which caches the images and cancels downloads if a cell goes off-screen. At the moment each image is downloaded again when the user scrolls and cellFoRow is called again for this particular cell.

不过你应该使用一个下载管理器来缓存图像,如果一个单元离开屏幕就取消下载。此时,当用户滚动时再次下载每个图像,并再次为该特定单元调用cellFoRow。