Почему обновление пользовательского интерфейса из фонового потока занимает так много времени?

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

Но чисто ради более глубокого понимания того, как работают GCD и диспетчеризация main:

У меня есть кнопка, которая запускает сетевой вызов, и в его завершенииHandler я в конечном итоге делаю:

self.layer.borderColor = UIColor(red: 255/255.0, green: 59/255.0, blue: 48/255.0, alpha: 1.0).cgColor
self.layer.borderWidth = 3.0

Для изменения цвета требуется 6-7 секунд. Очевидно, что если запустить приведенный выше код из основного потока, он немедленно изменит цвет границы.

Вопрос1, хотя у меня нет НИКАКОГО другого кода для запуска, почему изменения пользовательского интерфейса не происходят сразу из фонового потока? Чего ждать?

Интересно, однако, что если я нажму кнопку, чтобы сделать сетевой вызов, а затем нажму на само текстовое поле (до 6-7 секунд), цвет границы немедленно изменится.

Это происходит из-за:

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

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

Вопрос 2. Верно ли это наблюдение?

Если я не нажму и просто подожду:

введите здесь описание изображения

Если я нажму:

введите здесь описание изображения


Мой полный код выглядит следующим образом:

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var textField: UITextField!

    @IBAction func isValid(_ sender: Any) {

        let userEmail = textField.text

        let requestURL = NSURL(string: "https://jsonplaceholder.typicode.com")

        var request = URLRequest(url: requestURL as! URL)

        request.httpMethod = "POST"

        let postString = "Anything"

        request.httpBody = postString.data(using: .utf8)

        let task = URLSession.shared.dataTask(with: request) { data, response, error in

            guard let data = data, error == nil else {
                print("error=\(error)")
                return
            }

            if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
            }

            do {

                let json = try? JSONSerialization.jsonObject(with: data, options: [])

                if let _ = json as? [String: Any] {

                    self.textField.layer.borderColor = UIColor(red: 255/255.0, green: 59/255.0, blue: 48/255.0, alpha: 1.0).cgColor
                    self.textField.layer.borderWidth = 3.0

                }

            } catch let error as NSError {
                print(error)
            }

        }
        task.resume()

    }
    override func viewDidLoad() {
        super.viewDidLoad()
    }

}

person Honey    schedule 05.07.2017    source источник
comment
Первое предложение вашего вопроса неверно: я понимаю, что все обновления пользовательского интерфейса должны выполняться из фонового потока. следует читать Я понимаю, что все обновления пользовательского интерфейса должны выполняться из основного потока. (кстати, я не тот, кто проголосовал за вас)   -  person Duncan C    schedule 05.07.2017
comment
@DuncanC ой. Спасибо. Отредактировано...   -  person Honey    schedule 05.07.2017
comment
Ваше наблюдение звучит разумно и может быть правильным. Но UIKit не с открытым исходным кодом. Apple требует, чтобы обновления пользовательского интерфейса выполнялись в основном потоке, и мы можем только догадываться, что произойдет в противном случае.   -  person Martin R    schedule 05.07.2017
comment
@MartinR Я действительно пытаюсь настроить свой мозг и глубже понять принятие решений в GCD + iOS. Почему ждет 6-7 секунд? Можете ли вы сделать обоснованное предположение?   -  person Honey    schedule 05.07.2017
comment
Даже если бы я мог - это было бы просто предположением. Если в этой ветке не появится инженер Apple и не ответит на ваш вопрос, нам остается только догадываться. – (И угадывание — это не то, для чего предназначен SO. На самом деле у меня есть соблазн проголосовать за закрытие, но я не уверен, что выбрать в качестве причины :)   -  person Martin R    schedule 05.07.2017
comment
@MartinR Хорошо, никаких догадок. Давайте просто держать открытыми. Возможно, появится инженер Apple или кто-то, кто знает откуда-то еще или просто знает больше, чем мы.   -  person Honey    schedule 05.07.2017
comment
Почему вы тратите время на то, чтобы возиться с поведением, которое Apple специально говорит вам НЕ делать? Независимо от того, какую проблему вы пытаетесь решить, вы просто НИКОГДА не должны этого делать. Без исключений.   -  person GetSwifty    schedule 05.07.2017
comment
@PEEJWEEJ Разве не очевидно, что я не собираюсь этого делать. Так я лучше понимаю вещи и лучше отлаживаю. Все разные.   -  person Honey    schedule 05.07.2017
comment
Каждому свое... но ответ на это будет таким же, как и на все, что вы делаете, что API запрещает вам делать. Вы никогда не должны этого делать, никто без доступа к исходному коду не может дать реальный ответ, и результаты могут меняться от версии к версии.   -  person GetSwifty    schedule 05.07.2017
comment
@MartinR Есть ли у вас какие-либо мысли или вы знаете какую-либо документацию Apple о этот комментарий?   -  person Honey    schedule 05.07.2017
comment
Я подозреваю, что даже инженер Apple не может сказать вам, что происходит. Когда вы пытаетесь обновить пользовательский интерфейс из фонового потока, вы создаете ошибки параллелизма. Код, работающий одновременно на разных ядрах, пытается получить доступ к одним и тем же аппаратным ресурсам в одно и то же время. (Память, аппаратное обеспечение дисплея и т. д.) То, что происходит в этом случае, скорее всего, фактически непредсказуемо. это зависит от условий, которые меняются от запуска к запуску, и, таким образом, если у вас нет аппаратного эмулятора многоядерного процессора, вы не можете предсказать, что произойдет, или точно сказать, что произошло. Смотрите мой ответ.   -  person Duncan C    schedule 07.12.2019


Ответы (1)


Если вы попытаетесь выполнить обновления пользовательского интерфейса из фонового потока, «результаты не определены». Наиболее распространенный эффект, который я видел, - это то, что вы описываете, - очень длительные задержки до появления обновления. Второй наиболее распространенный эффект, который я видел, — это сбой. Третий наиболее распространенный эффект — это какой-то артефакт рисования.

Результаты выполнения обновлений пользовательского интерфейса из фонового потока действительно недетерминированы. У вас есть несколько процессорных ядер, одновременно обращающихся к одним и тем же аппаратным ресурсам, и точное время между этими обращениями неизвестно и бесконечно варьируется. Это было бы похоже на компьютер без дисплея, но с двумя клавиатурами и двумя мышами, и двумя операторами, одновременно редактирующими один и тот же документ. Действия каждого человека с клавиатурой изменят состояние документа и испортят изменения, которые пытался применить другой человек. Курсор был бы не в том месте. Объем текста в документе будет отличаться от ожидаемого. Положение прокрутки будет отключено. и т. д. и т. д.

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

Как говорит Мартин в своем комментарии, код UIKit является частным, поэтому мы не можем знать подробности того, что пойдет не так. Все, что мы знаем, это то, что плохие вещи случаются, поэтому НЕ ДЕЛАЙТЕ ЭТОГО.

person Duncan C    schedule 05.07.2017
comment
хм. Вы хорошо раскрыли проблему. Так может быть, фоновый поток такой: Почему вы делаете обновления пользовательского интерфейса, используя меня?!! Я не тот, кто отвечает за управление изменениями пользовательского интерфейса... но теперь, поскольку вы попросили меня позволить мне подождать, чтобы увидеть, есть ли еще какие-либо идиотские изменения пользовательского интерфейса, сделанные в фоновом режиме, а затем все, я сгруппирую их все вместе и сделаю это. ... это может быть не идеально ... Я могу просто вылететь или создать артефакт рисования. Пожалуйста, не делайте этого со мной снова + я предполагаю, что Apple также может ввести эту задержку, чтобы разработчики могли раньше обнаруживать ошибки. - person Honey; 05.07.2017
comment
Как указывалось ранее, мы не можем знать наверняка, поскольку UIKit является частной собственностью, но я считаю, что в основном это связано с очередями приоритетов и тем, как они работают. Поскольку фоновая очередь имеет низкий приоритет, она будет выполнена через некоторое время в будущем, и мы не можем этого знать. Чтобы понять это лучше, вы должны знать, как работает планировщик, чтобы все это имело смысл. Я бы порекомендовал книги Зильбершатца или Таненбаума об операционных системах. - person henrique; 05.07.2017
comment
Когда вы вносите изменения в пользовательский интерфейс в фоновом режиме, эти изменения все еще в конечном счете проходят через основной поток. Но время неизвестно? Или технически у вас может быть 20 фоновых потоков... все они вносят изменения в пользовательский интерфейс в неизвестное время?! Из того, что вы сказали, это 2-й, но есть ли у вас для этого какая-то причина? - person Honey; 05.07.2017
comment
@valcanaia Поскольку фоновая очередь имеет низкий приоритет, она будет выполнена через некоторое время в будущем, и мы не можем знать ‹-- Я думаю, вы ошиблись. Если вы поместите точку останова, которая имеет звук только в: self.textField.layer.borderColor, вы услышите звуковой сигнал за 6-7 секунд до фактического изменения пользовательского интерфейса. Очевидно, что строка выполняется. Но это вводит в заблуждение... Установка self.textField.layer.borderColor не сразу изменила бы пользовательский интерфейс, а изменила бы только модель. Возможно, существует несколько невидимых шагов между фактическим изменением пользовательского интерфейса и простым изменением значения. - person Honey; 05.07.2017
comment
@valcanaia что делает DispatchQueue.main.async{...}, так это каким-то образом вызывает немедленное изменение пользовательского интерфейса. Опять же, «изменение пользовательского интерфейса» отличается от «изменения модели/кода». - person Honey; 05.07.2017
comment
Да, это та часть, где UIKit является частной собственностью. Просто представьте, что просто выполнение self.textField.layer.borderColor = someColor включает в себя получение каждой из переменных, а затем, наконец, их установку. Если вы сделаете шаг за шагом эту строку, вы увидите, что Xcode застревает в этой строке несколько раз, по одному для получения каждой переменной (self, textField, layer, borderColor). Выполнение этой строки не является атомарным и, что еще хуже, происходит в фоновом потоке. - person henrique; 05.07.2017
comment
Нет, DispatchQueue.main.async { code } ничего не навязывает. Сама архитектура заставляет помещать код в основную очередь выполнения, которая имеет более высокий (самый высокий) приоритет, чем другие. Например, если вы используете .sync вместо .async, вы увидите разницу, вы увидите, что даже в основном потоке ваше обновление пользовательского интерфейса не будет выполнено немедленно. - person henrique; 05.07.2017