Обновление пользовательского интерфейса с помощью 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 ответ

Решение

Три замечания, два основных, одно немного более продвинутое:

  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)
            }
        }
    }
    
  2. Также обратите внимание, что прогресс - это число от 0,0 до 1,0, поэтому вы, вероятно, хотите разделить максимальное количество итераций для цикла.

  3. Если обновления пользовательского интерфейса происходят из фонового потока быстрее, чем пользовательский интерфейс может их обработать, основной поток может запаздывать с запросами на обновление (что выглядит намного медленнее, чем на самом деле). Чтобы решить эту проблему, можно рассмотреть возможность использования источника отправки для отделения задачи "обновить пользовательский интерфейс" от фактического процесса фонового обновления.

    Можно использовать 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);
        }
    }
    
Другие вопросы по тегам