2014-11-04 14 views
9

W moim kodzie mam prostą pętlę for, która wykonuje pętle 100 razy z zagnieżdżonymi pętlami, aby utworzyć opóźnienie. Po opóźnieniu aktualizuję element widoku postępu w interfejsie za pomocą funkcji dispatch_async. Jednak nie mogę zaktualizować interfejsu użytkownika. Czy ktoś wie, dlaczego interfejs użytkownika nie aktualizuje? Uwaga: Poniższa instrukcja print służy do sprawdzenia, czy pętla for poprawnie się zapętla.Aktualizowanie interfejsu użytkownika za pomocą funkcji Dispatch_Async w 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

Czy możesz potwierdzić, że wezwanie jest wypalanie setProgress? Spróbuj rzucić niektóre logowania tam lub punkt przerwania logowania. Zasadniczo lepiej jest używać NSOperation dla rzeczy, które używają kakao. – macshome

+0

Interesujące, wrzuciłem część logowania i wygląda na to, że wywołanie setProgress nie uruchamia się. Dlaczego miałoby to być? – dslkfjsdlfjl

+0

Cóż, kiedy dispatch_async to zależy od systemu, kiedy ma się wystrzelić. Jeśli potrzebujesz UI do aktualizacji w odpowiednim czasie, nie korzystaj z asynchronizacji. W Xcode możesz wstrzymać aplikację i obejrzeć również oczekujące bloki. – macshome

Odpowiedz

33

Trzy obserwacje, dwa podstawowe, jeden trochę bardziej zaawansowanych:

  1. pętla nie będzie w stanie zaktualizować UI w tym wątku głównego, chyba że sama pętla jest uruchomiony w innym wątku. Tak, możesz wysłać go do kolejki w tle. W 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) 
         } 
        } 
    } 
    

    w 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. Należy również pamiętać, że postęp jest liczbą od 0.0 do 1.0, więc prawdopodobnie chcesz podzielić przez maksymalną liczbę iteracji dla pętla.

  3. Jeśli aktualizacje interfejsu użytkownika pojawiają się szybciej z wątku w tle, niż interfejs użytkownika może sobie z nimi poradzić, główny wątek może zostać zignorowany za pomocą żądań aktualizacji (co sprawia, że ​​wygląda znacznie wolniej niż w rzeczywistości). Aby rozwiązać ten problem, można rozważyć użycie źródła wysyłki do oddzielenia zadania "zaktualizuj interfejs użytkownika" od faktycznego procesu aktualizacji.

    Można użyć DispatchSourceUserDataAdd (w Swift 2, to dispatch_source_t z DISPATCH_SOURCE_TYPE_DATA_ADD), zakładać add połączenia (dispatch_source_merge_data w Swift 2) z gwintem tle tak często, jak to potrzebne, a interfejs użytkownika będzie przetwarzać je tak szybko jak to możliwe , ale połączy je razem, gdy wywoła data (dispatch_source_get_data w Swift 2), jeśli aktualizacje w tle pojawiają się szybciej niż interfejs może w inny sposób je przetworzyć. Zapewnia to maksymalną wydajność tła przy optymalnych aktualizacjach interfejsu użytkownika, ale co ważniejsze, gwarantuje to, że interfejs użytkownika nie stanie się wąskim gardłem.

    Więc najpierw zadeklarować jakąś zmienną śledzić postęp:

    var progressCounter: UInt = 0 
    

    A teraz pętla może utworzyć źródło, określić, co zrobić, gdy źródłem jest aktualizowany, a następnie uruchomić asynchroniczne pętlę, która aktualizuje źródło. W Swift 3 czyli:

    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) 
        } 
    } 
    

    w 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

Dziękuję bardzo!Bardzo pouczające i robi dokładnie to, co musiałem zrobić! – dslkfjsdlfjl

Powiązane problemy