Updating the UI Using Dispatch_Async in Swift

后端 未结 1 1539
[愿得一人]
[愿得一人] 2020-11-29 04:45

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

相关标签:
1条回答
  • 2020-11-29 05:25

    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);
          }
      }
      
    0 讨论(0)
提交回复
热议问题