14

In my code I have a simple for loop that loops 100 times with nested for loops to create a delay. After the delay, I am updating a progress view element in the UI through a dispatch_async. However, I cannot get the UI to update. Does anyone know why the UI is not updating? Note: The print statement below is used to verify that the for loop is looping correctly.

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)

    }
  }
dslkfjsdlfjl
  • 145
  • 1
  • 2
  • 6
  • 1
    Can you confirm that the setProgress call is firing? Try tossing some logging in there or a logging breakpoint. Also it's generally preferable to use NSOperation for things that are using Cocoa. – macshome Nov 04 '14 at 19:44
  • Interesting, I threw some logging in and it appears the setProgress call is not firing. Why would that be? – dslkfjsdlfjl Nov 04 '14 at 19:56
  • Well, when you dispatch_async it's up to the system as to when it is going to fire. If you need the UI to update in a timely manner then don't use async. In Xcode you can pause the app and take a look at the pending blocks as well. – macshome Nov 04 '14 at 20:14
  • What should I use then to update the UI? – dslkfjsdlfjl Nov 04 '14 at 20:29
  • 1
    Is the loop that's doing this (`for i in 0..<100`) running in a different async queue, or is it just running in the main queue? – Nate Cook Nov 04 '14 at 20:32

1 Answers1

43

Three observations, two basic, one a little more advanced:

  1. Your loop will not be able to update the UI in that main thread unless the loop itself is running on another thread. So, you can dispatch it to some background queue. In 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)
            }
        }
    }
    

    In 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. Also note that the progress is a number from 0.0 to 1.0, so you presumably want to divide by the maximum number of iterations for the loop.

  3. If UI updates come more quickly from the background thread than the UI can handle them, the main thread can get backlogged with update requests (making it look much slower than it really is). To address this, one might consider using dispatch source to decouple the "update UI" task from the actual background updating process.

    One can use a DispatchSourceUserDataAdd (in Swift 2, it's a dispatch_source_t of DISPATCH_SOURCE_TYPE_DATA_ADD), post add calls (dispatch_source_merge_data in Swift 2) from the background thread as frequently as desired, and the UI will process them as quickly as it can, but will coalesce them together when it calls data (dispatch_source_get_data in Swift 2) if the background updates come in more quickly than the UI can otherwise process them. This achieves maximum background performance with optimal UI updates, but more importantly, this ensures the UI won't become a bottleneck.

    So, first declare some variable to keep track of the progress:

    var progressCounter: UInt = 0
    

    And now your loop can create a source, define what to do when the source is updated, and then launch the asynchronous loop which updates the source. In Swift 3 that is:

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

    In 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);
        }
    }
    
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Thank you a lot! Very informative and it does exactly what I needed to do! – dslkfjsdlfjl Nov 04 '14 at 21:45
  • So your point #3 is interesting, but when would this ever actually come into play? Can you give a real-world example of where you've had to implement an adder dispatch source? – Gargoyle Mar 24 '19 at 22:29
  • You’ll need this in compute-heavy apps. E.g., if you ever do your own image processing pixel by pixel. Or generating a Mandelbrot set like here: https://stackoverflow.com/a/39949292/1271826. Or maybe you wanted to monitor progress of some home-grown compression algorithm or signal processing app. Bottom line, you can use this any time you have a background thread doing many simple calculations. It’s not terribly common, but in this class of problem, it can be very useful. – Rob Mar 24 '19 at 23:05