Добавление в UIStackView в UITableView из фонового потока - PullRequest
0 голосов
/ 07 февраля 2019

У меня есть UITableViewCell с горизонтальной UIStackView внутри.Создание и добавление UIViews в представление стека вызывает небольшую проблему с производительностью.В прокрутке представления таблицы есть определенный blip .Я профилировал его в инструментах, и призыв к обновлению стекового представления является вторым по величине во всем приложении.

Настройка выглядит следующим образом.Я использую MVP и в моем презентаторе для ячейки я вызываю updateStackView(with items: [Any]), когда я настраиваю ячейку, и я настраиваю ячейку методом cellForRowAtIndexPath.

Мой оригинальный updateStackView(with items: [Any]) было следующим:

func updateStackView(_ items: [Any]) {
    items.forEach { item in
        if let customViewModel = item as? CustomViewModel {
            myStackView.addArrangedSubview(CustomView(customViewModel))
        } else if item is Spacing {
            myStackView.addArrangedSubview(PipeSpacerView())
        }
    }
}

Так что это было узким местом, и я решил переместить некоторые вещи на задний план следующим образом:

func updateStackView(_ items: [Any]) {
    DispatchQueue.global(qos: .background).async {
        items.forEach { item in
            if let customViewModel = item as? CustomViewModel {
                DispatchQueue.main.async {
                    self.myStackView.addArrangedSubview(CustomView(customViewModel))
                }
            } else if item is Spacing {
                DispatchQueue.main.async {
                    self.myStackView.addArrangedSubview(PipeSpacerView())
                }
            }
        }
    }
}

Какая-то уродливая, поскольку она имеет некоторое глубокое вложение, но оказал большое влияние на производительность.

Я был доволен этим, пока не прокрутил вниз до нижней части моего представления таблицы и не начал прокручивать обратно вверх.Я заметил, что некоторые из CustomViews были в представлении стека дважды, когда ячейка прокручивалась в поле зрения.Как только ячейка была полностью видна, представление стека исправило себя большую часть времени назад к правильному номеру.

В моем prepareForReuse я специально вызываю следующую функцию:

func resetDescriptionStackView() {
    descriptionStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
}

, который всегда работал до штрафа, когда все было в главном потоке.

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

Ответы [ 2 ]

0 голосов
/ 07 февраля 2019

Прежде всего. Background Quality of service используется в основном для большой / тяжелой фоновой работы (например, отправка статистических данных, синхронизация базы данных с сервером и т. Д.), Но когда вы хотите выполнить небольшую работу в очереди, отличной от основнойа затем обновите пользовательский интерфейс надлежащим образом, используя .userInitiated или .utility.Также я рекомендую использовать ваш собственный OperationQueue с одним из фоновых Qos.
Чтобы сделать код лучше, вы можете использовать оператор «defer» ( документация ) и разделять код для небольших функций.

Если вы хотите добавить подпредставление, вы можете добавить специальный идентификатор для своего CustomView и Spacing, а затем проверить его.Полный код:

//For each our CustomView and Spacing we set uniq id to tag property in init of each class.

        /// Operation queue that work with our UIStackView
        fileprivate lazy var stackViewOperationQueue: OperationQueue = {

            let queue = OperationQueue()
            queue.name = "StackView OperationQueue"
            queue.qualityOfService = .userInitiated
            return queue
        }()

        func updateStackView(_ items: [Any]) {

            stackViewOperationQueue.addOperation { [weak self] in
                items.forEach { self?.addArrangedSubviewToStackView(by: $0) }
            }
        }

       fileprivate func addArrangedSubviewToStackView(by item: Any) {

            var handler: () -> () = {}

            defer {
                DispatchQueue.main.async {
                    handler()
                }
            }

            if let customViewModel = item as? CustomViewModel {
                handler = { [weak self] in
                  let viewToAdd = CustomView(customViewModel)
                  self?.checkAndAddSubviewIfNeeded(viewToAdd)}
            }
            else if item is Spacing {
                handler = { [weak self] in
                  self?.checkAndAddSubviewIfNeeded(PipeSpacerView()) }
            }
        }

       func checkAndAddSubviewIfNeeded(_ subviewToAdd: UIView) {

           if !myStackView.arrangedSubviews.contains(where: { view in return view.tag == subviewToAdd.tag }) {
               self.myStackView.addArrangedSubview(subviewToAdd)
           }
       }

Надеюсь, это поможет!

0 голосов
/ 07 февраля 2019

Ваша проблема связана с повторным использованием ячеек в табличном представлении.

Что происходит, когда вы прокручиваете вниз текущие видимые ячейки, уже запускается их фоновая задача для создания представлений.К тому времени, когда они заканчивают, текущие видимые ячейки изменились, чтобы отобразить текущие данные, но они все еще являются теми же экземплярами ячеек, которые использовались ранее в табличном представлении.Так что теперь два замыкания добавят представления к одному и тому же экземпляру стекового представления.(одно закрытие до повторного использования ячейки и другое закрытие после повторного использования ячейки)

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

например:

class CustomViewCell: UITableViewCell
{
    private var cellId: String = ""

    // call this method for each cell in cellForItemAt implementation
    func configure(_ items: [Any])
    {
        cellId = UUID() // generates a randomized string

        updateStackView(items)      
    }

    private func updateStackView(_ items: [Any]) {
        let curretCellId = cellId
        DispatchQueue.global(qos: .background).async {
            items.forEach { item in
                if let customViewModel = item as? CustomViewModel {
                    DispatchQueue.main.async {
                        if (self.cellId == currentCellId)
                        {
                            self.myStackView.addArrangedSubview(CustomView(customViewModel))
                        }
                    }
                } else if item is Spacing {
                    DispatchQueue.main.async {
                        if (self.cellId == currentCellId)
                        {
                            self.myStackView.addArrangedSubview(PipeSpacerView())
                        }
                    }
                }
            }
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...