iOS OperationQueue: экран блокировки одновременных асинхронных операций - PullRequest
0 голосов
/ 09 апреля 2019

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

У меня есть один URL-адрес в качестве отправной точки, к которому я делаю запрос, проверяю некоторую информацию и извлекаю все URL-адреса, содержащиеся в этой сети-page.После этого мне нужно делать то же самое с новыми URL-адресами снова и снова, пока не будет проверено определенное количество URL-адресов.

Я решил использовать Операции и OperationQueues , потому что мне также нужно иметь возможность выбрать максимальное количество одновременных операций.

Я создал пользовательскую асинхронную операцию LoadOperation и применил ее к каждому URL-адресу, хранящемуся в массиве.В блоке завершения я добавляю новые URL-адреса в массив и вызываю ту же функцию, рекурсивно.

Также я создал две очереди:

  • Параллельная для запросов,

и

  • Последовательный для доступа к общим свойствам следующим образом:
class Queue<Item: Equatable> {

    // concurrent queue for URL-requests
    let loadOperationQueue = OperationQueue() // qos: .utilities

    // serial operation queue for accessing shared properties 
    let serialOperationQueue = OperationQueue() // qos: .utilities

    // maximum number of URLs that need to be checked ultimately
    var maxNumberOfItems: Int = 1

    var pendingItems: [Item] = []   // newly added URLs
    var loadingItems: [Item] = []   // loading URLs
    var processedItems: [Item] = [] // URLs that were checked

    // all URLs for tableView dataSource
    var allItems: [Item] {
        return processedItems + loadingItems + pendingItems
    }

    func addToPending(_ item: Item) {
        guard allItems.count < maxNumberOfItems else {return}
        pendingItems.append(item)
    }

    func removeFromPending() -> Item? {
        guard !self.pendingItems.isEmpty else { return nil }
        let item = pendingItems.removeFirst()
        loadingItems.append(item)
        return item
    }

    func addToProcessed(_ item: Item) {
        loadingItems = loadingItems.filter{$0 != item}
        processedItems.append(item)

    }

    func isFull() -> Bool {
        return allItems.count >= maxNumberOfItems
    }
}

Это моя функция поиска:

    func startSearching() {

        // iterate over array of URLs creating async operation
        while let currentWebPage = queue.removeFromPending() {

            let loadOperation = LoadOperation(currentWebPage, searchString: searchString)

            loadOperation.completionBlock = {
                let webPage = loadOperation.output!

                self.queue.serialOperationQueue.addOperation {

                    // add checked URL to processed
                    self.queue.addToProcessed(webPage)

                    // add new urls to an array of pending URLs
                    for urlString in webPage.containedLinks {
                        //check if the limit of checked urls is not exceeded
                        guard !self.queue.isFull() else { return }
                        //check if this url is already in queue
                        guard !self.queue.allItems.contains(where: {$0.url == urlString}) else { continue }

                        self.queue.addToPending(WebPage(url: urlString, containedLinks: [], status: .pending))
                    }

                    // update UI
                    OperationQueue.main.addOperation {
                        self.viewDelegate?.reloadTable()
                    }

                    // repeat searching process with new urls
                    self.startSearching()
                }
            }
            queue.loadOperationQueue.addOperation(loadOperation)
        }

    }

Не могу понять, почему:

  1. Когда я запускаю приложение, экран зависает .Даже если все мои очереди находятся в фоновом режиме (qos: utilities).

  2. Иногда, когда я пытаюсь прокрутить UITableView, я получаю sigabort из-за индекса вне диапазона , хотя я пытаюсь получить доступ ко всем свойствам в последовательной очереди.

Это исходный код данных:

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

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "WebPageTableViewCell", for: indexPath) as! WebPageTableViewCell
        let (url, status) = presenter.getCellContent(at: indexPath.row)
        cell.addressLabel.text = url
        cell.statusLabel.text = status
        return cell
    }

И функции из presenter:

    func getNumberOfRows() -> Int {
        return queue.allItems.count
    }

    func getCellContent(at index: Int) -> (url: String, status: String) {
        return (url: queue.allItems[index].url, status: queue.allItems[index].status.description)
    }
...