使用DispatchQueue索引超出范围异常

时间:2018-11-23 16:25:21

标签: ios swift grand-central-dispatch

我有以下代码显示了UITableView.中的测验列表,问题是要显示我调用方法prepareImages的图像,并且在填充图像时出现索引超出范围的异常tableView函数中的单元格,因为似乎quizzesImages数组为空(print(self.quizzesImages.count)显示0),我知道它与如何使线程工作有关但我看不到我要去哪里。

import UIKit
 // Estructura del JSON que devuelve la URL
struct ResponseObject : Codable {
let quizzes : [Quiz]?
let pageno : Int?
    let nextUrl : String?
}

class QuizzesTableViewController: UITableViewController {

// Aquí se guardan los quizzes cargados de la URL
var totalQuizzes = [Quiz]()
var quizzesImages = [UIImage]()

override func viewDidLoad() {
    super.viewDidLoad()
    self.tableView.rowHeight = 90.0
    navigationController?.navigationBar.prefersLargeTitles = true
    downloadQuizzes()
}

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

func prepareImages(){
    for i in 0...self.totalQuizzes.count-1{

        let imageUrlString = self.totalQuizzes[i].attachment?.url
        let imageUrl:URL = URL(string: imageUrlString!)!
        print(imageUrl)

        // Start background thread so that image loading does not make app unresponsive
        DispatchQueue.global(qos: .userInitiated).async {
            let imageData:NSData = NSData(contentsOf: imageUrl)!

            // When from background thread, UI needs to be updated on main_queue
            DispatchQueue.main.async {
                let image = UIImage(data: imageData as Data)
                print("hola")
                self.quizzesImages.append(image!)
            }
        }
    }

}

func downloadQuizzes(){
    let QUIZZES_URL = "https://quiz2019.herokuapp.com/api/quizzes?token=945d3bf7d4c709d69940"
    if let url = URL(string: QUIZZES_URL){
        let queue = DispatchQueue(label: "download quizzes queue")
        queue.async {
            DispatchQueue.main.async {
                UIApplication.shared.isNetworkActivityIndicatorVisible = true
            }
            defer{
                DispatchQueue.main.async {
                    UIApplication.shared.isNetworkActivityIndicatorVisible = false
                }
            }
            let data = try? Data(contentsOf: url, options: .alwaysMapped)
            let decoder = JSONDecoder()
            do{
                let response = try decoder.decode(ResponseObject.self, from: data!)
                DispatchQueue.main.async {
                    if (response.quizzes!.count != 0){
                        self.totalQuizzes.append(contentsOf: response.quizzes!)
                        self.prepareImages()
                        print(self.totalQuizzes.count)
                        print(self.quizzesImages.count)
                        self.tableView.reloadData()
                    }
                }
            }
            catch {
                print(error)
            }
        }
    }
}
// MARK: - Table view data source

override func numberOfSections(in tableView: UITableView) -> Int {
    // #warning Incomplete implementation, return the number of sections
    return 1
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // #warning Incomplete implementation, return the number of rows
    return totalQuizzes.count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Quiz", for: indexPath) as! QuizTableViewCell

    let quiz = totalQuizzes[indexPath.row]
    let images = quizzesImages[indexPath.row]
    cell.authorLabel?.text = quiz.author?.username
    cell.quizLabel?.text = quiz.question
    cell.quizImage?.image = images
    return cell
}}

预先感谢您的帮助!

2 个答案:

答案 0 :(得分:1)

由于您没有说异常发生在哪一行,所以我最好的猜测是它发生在这一行。

let images = quizzesImages[indexPath.row]

问题出在downloadQuizzes中。在那儿您呼叫prepareImages,然后几行呼叫reloadData。由于prepareImages执行异步任务,因此它不会阻止downloadQuizzes完成运行。这将导致reloadData的异步部分完成之前调用prepareImages

在调用prepareImages之前,您需要确保reloadData已完成运行。

另一种选择是使用像Kingfisher这样的库,该库仅在需要在单元格中显示图像时才下载图像,而不是全部下载并减慢显示表格的速度。


附加说明:

您不应该使用Data(contentsOf:)加载远程内容,URLSession是正确的选择。

答案 1 :(得分:1)

See this screen shot form my playground

我尝试在操场上运行您的代码,并设法使其进行了一些更改,请参见以下内容。

  

注意

要使其在操场上运行,我必须做出一些假设,只是用我的逻辑替换您的核心逻辑。

快乐编码

import UIKit
// Estructura del JSON que devuelve la URL

struct ResponseObject: Codable {
    let quizzes: [Quiz]?
    let pageno: Int?
    let nextURL: String?

    enum CodingKeys: String, CodingKey {
        case quizzes, pageno
        case nextURL = "nextUrl"
    }
}

struct Quiz: Codable {
    let id: Int?
    let question: String?
    let author: Author?
    let attachment: Attachment?
    let favourite: Bool?
    let tips: [String]?
}

struct Attachment: Codable {
    let filename: String?
    let mime: MIME?
    let url: String?
}

enum MIME: String, Codable {
    case imageJPEG = "image/jpeg"
}

struct Author: Codable {
    let id: Int?
    let isAdmin: Bool?
    let username: String?
}


class QuizzesTableViewController: UITableViewController {

    // Aquí se guardan los quizzes cargados de la URL
    var totalQuizzes = [Quiz]()
    var quizzesImages = [UIImage]()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.tableView.rowHeight = 90.0
        navigationController?.navigationBar.prefersLargeTitles = true
        self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Quiz")
        self.tableView.dataSource = self
        self.tableView.delegate = self
        downloadQuizzes()
    }

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

    func prepareImages(){
        print("in prepare images")
        // Start background thread so that image loading does not make app unresponsive
        DispatchQueue.global(qos: .userInitiated).async {
            for i in 0...self.totalQuizzes.count-1 {

                let imageUrlString = self.totalQuizzes[i].attachment?.url
                let imageUrl:URL = URL(string: imageUrlString!)!
                print(imageUrl)

            let imageData:NSData = NSData(contentsOf: imageUrl)!
            let image = UIImage(data: imageData as Data)
            print("hola \(i)")
            self.quizzesImages.append(image!)
            print(self.quizzesImages.count)
            // When from background thread, UI needs to be updated on main_queue
            }
            DispatchQueue.main.async {
                print(self.quizzesImages.count)
                self.tableView.reloadData()
                UIApplication.shared.isNetworkActivityIndicatorVisible = false
            }
        }
    }

    func downloadQuizzes(){
        let QUIZZES_URL = "https://quiz2019.herokuapp.com/api/quizzes?token=945d3bf7d4c709d69940"
        if let url = URL(string: QUIZZES_URL){
            let queue = DispatchQueue(label: "download quizzes queue")
            queue.async {
                DispatchQueue.main.async {
                    UIApplication.shared.isNetworkActivityIndicatorVisible = true
                }
                let data = try? Data(contentsOf: url, options: .alwaysMapped)
                let decoder = JSONDecoder()
                do{
                    let response = try decoder.decode(ResponseObject.self, from: data!)
                    DispatchQueue.main.async {
                        if (response.quizzes!.count != 0){
                            self.totalQuizzes.append(contentsOf: response.quizzes!)
                            self.prepareImages()
                            print(self.totalQuizzes.count)
                        }
                    }
                }
                catch {
                    print(error)
                }
            }
        }
    }
    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int {
        // #warning Incomplete implementation, return the number of sections
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete implementation, return the number of rows
        print("num of rows \(totalQuizzes.count)")
        return totalQuizzes.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        print("in cellForRowAt \(indexPath.row)")
        let cell = tableView.dequeueReusableCell(withIdentifier: "Quiz")!
        let quiz = totalQuizzes[indexPath.row]
        let images = quizzesImages[indexPath.row]
        cell.textLabel?.text = "\(quiz.author?.username) \(quiz.question)"
        cell.imageView?.image = images
        return cell
    }}