Индекс вне диапазона исключений, используя DispatchQueue - PullRequest
0 голосов
/ 23 ноября 2018

У меня есть следующий код, который показывает список тестов в 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 голосов
/ 23 ноября 2018

См. Этот снимок экрана с моей игровой площадки

Я попытался запустить ваш код на игровой площадке и сумел заставить его работать с некоторыми изменениями, см. Следующее.

Примечание

Чтобы запустить его на игровой площадке, я должен сделать некоторые предположения, просто заменив основную логику моей.

Happy Coding 101

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
    }}
0 голосов
/ 23 ноября 2018

Поскольку вы не сказали, по какой строке происходит исключение, я думаю, что это происходит по этой строке.

let images = quizzesImages[indexPath.row]

Проблема в downloadQuizzes.Там вы звоните prepareImages, а через пару строк звоните reloadData.Поскольку prepareImages выполняет асинхронную задачу, downloadQuizzes не блокирует завершение работы.Это заставляет reloadData вызываться до завершения асинхронной части prepareImages.

Вам необходимо убедиться, что prepareImages завершен, прежде чем вызывать reloadData.

Другим вариантом является использование библиотеки, такой как Kingfisher, которая будет загружать изображения только тогда, когда они должны быть отображены в ячейке, а не загружать их все и замедлять отображение таблицы.


Дополнительные примечания:

Вы не должны использовать Data(contentsOf:) для загрузки удаленного контента, URLSession является правильным вариантом для него.

...