2014-11-04 2 views
9

В моем коде у меня есть простой цикл цикла, который петли 100 раз с вложенными циклами для создания задержки. После задержки я обновляю элемент представления прогресса в пользовательском интерфейсе через dispatch_async. Однако я не могу обновить пользовательский интерфейс. Кто-нибудь знает, почему пользовательский интерфейс не обновляется? Примечание. Приведенный ниже оператор печати используется для проверки правильности петли for.Обновление пользовательского интерфейса с помощью Dispatch_Async в Swift

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) 

    } 
    } 
+0

Можете ли вы подтвердить, что вызов setProgress стреляет? Попытайтесь бросить туда какую-то регистрацию или контрольную точку регистрации. Также обычно предпочтительнее использовать NSOperation для вещей, которые используют Cocoa. – macshome

+0

Интересно, я бросил несколько протоколов, и кажется, что вызов setProgress не срабатывает. Почему это так? – dslkfjsdlfjl

+0

Ну, когда вы отправляете_асинк, это зависит от системы, когда она собирается срабатывать. Если вам нужно, чтобы пользовательский интерфейс обновлялся своевременно, не используйте async. В Xcode вы можете приостановить приложение и взглянуть на ожидающие блоки. – macshome

ответ

33

Три наблюдение, два основных, один чуть более продвинутое:

  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. Если обновление пользовательского интерфейса происходит быстрее из фонового потока, чем пользовательский интерфейс может обрабатывать их, основной поток может зависеть от запросов на обновление (что делает его намного медленнее, чем на самом деле). Чтобы решить эту проблему, можно рассмотреть возможность использования источника отправки, чтобы отделить задачу «Обновление UI» от фактического процесса обновления фона.

    можно использовать 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); 
        } 
    } 
    
+0

Большое вам спасибо!Очень информативно, и он делает именно то, что мне нужно было сделать! – dslkfjsdlfjl

Смежные вопросы