Обновление пользовательского интерфейса с помощью Dispatch_Async в Swift
В моем коде у меня есть простой цикл for, который повторяется 100 раз с вложенными циклами for для создания задержки. После задержки я обновляю элемент представления прогресса в пользовательском интерфейсе через dispatch_async. Тем не менее, я не могу получить интерфейс для обновления. Кто-нибудь знает, почему интерфейс не обновляется? Примечание. Оператор print ниже используется для проверки правильности цикла for.
for i in 0..<100 {
//Used to create a delay
for var x = 0; x<100000; x++ {
for var z = 0; z<1000; z++ {
}
}
println(i)
dispatch_async(dispatch_get_main_queue()) {
// update some UI
self.progressView.setProgress(Float(i), animated: true)
}
}
1 ответ
Три замечания, два основных, одно немного более продвинутое:
Ваш цикл не сможет обновить пользовательский интерфейс в этом основном потоке, если сам цикл не выполняется в другом потоке. Таким образом, вы можете отправить его в некоторую фоновую очередь. В Swift 3:
DispatchQueue.global(qos: .utility).async { for i in 0 ..< kNumberOfIterations { // do something time consuming here DispatchQueue.main.async { // now update UI on main thread self.progressView.setProgress(Float(i) / Float(kNumberOfIterations), animated: true) } } }
В Swift 2:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { for i in 0 ..< kNumberOfIterations { // do something time consuming here dispatch_async(dispatch_get_main_queue()) { // now update UI on main thread self.progressView.setProgress(Float(i) / Float(kNumberOfIterations), animated: true) } } }
Также обратите внимание, что прогресс - это число от 0,0 до 1,0, поэтому вы, вероятно, хотите разделить максимальное количество итераций для цикла.
Если обновления пользовательского интерфейса происходят из фонового потока быстрее, чем пользовательский интерфейс может их обработать, основной поток может запаздывать с запросами на обновление (что выглядит намного медленнее, чем на самом деле). Чтобы решить эту проблему, можно рассмотреть возможность использования источника отправки для отделения задачи "обновить пользовательский интерфейс" от фактического процесса фонового обновления.
Можно использовать
DispatchSourceUserDataAdd
(в Swift 2 этоdispatch_source_t
изDISPATCH_SOURCE_TYPE_DATA_ADD
), сообщениеadd
звонки (dispatch_source_merge_data
в Swift 2) из фонового потока так часто, как это необходимо, и пользовательский интерфейс будет обрабатывать их так быстро, как может, но объединит их вместе, когда вызоветdata
(dispatch_source_get_data
в Swift 2) если фоновые обновления появляются быстрее, чем пользовательский интерфейс может их обработать. Это обеспечивает максимальную фоновую производительность с оптимальными обновлениями пользовательского интерфейса, но, что более важно, гарантирует, что пользовательский интерфейс не станет узким местом.Итак, сначала объявите некоторую переменную для отслеживания прогресса:
var progressCounter: UInt = 0
И теперь ваш цикл может создать источник, определить, что делать при обновлении источника, а затем запустить асинхронный цикл, который обновляет источник. В Swift 3 это:
progressCounter = 0 // create dispatch source that will handle events on main queue let source = DispatchSource.makeUserDataAddSource(queue: .main) // tell it what to do when source events take place source.setEventHandler() { [unowned self] in self.progressCounter += source.data self.progressView.setProgress(Float(self.progressCounter) / Float(kNumberOfIterations), animated: true) } // start the source source.resume() // now start loop in the background DispatchQueue.global(qos: .utility).async { for i in 0 ..< kNumberOfIterations { // do something time consuming here // now update the dispatch source source.add(data: 1) } }
В Swift 2:
progressCounter = 0 // create dispatch source that will handle events on main queue let source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue()); // tell it what to do when source events take place dispatch_source_set_event_handler(source) { [unowned self] in self.progressCounter += dispatch_source_get_data(source) self.progressView.setProgress(Float(self.progressCounter) / Float(kNumberOfIterations), animated: true) } // start the source dispatch_resume(source) // now start loop in the background dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { for i in 0 ..< kNumberOfIterations { // do something time consuming here // now update the dispatch source dispatch_source_merge_data(source, 1); } }