一尘不染

在Swift中使用Dispatch_Async更新UI

swift

在我的代码中,我有一个简单的for循环,使用嵌套的for循环循环100次以创建延迟。延迟之后,我将通过dispatch_async更新UI中的进度视图元素。但是,我无法更新UI。有谁知道为什么用户界面没有更新?注意:下面的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)

    }
  }

阅读 1743

收藏
2020-07-07

共1个答案

一尘不染

三种观察,两种基本观察,一种更为先进:

  1. 除非循环本身在另一个线程上运行,否则循环将无法更新该主线程中的UI。因此,您可以将其分派到某些后台队列。在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)
        }
    }
}
  1. 还要注意,进度是一个从0.0到1.0的数字,因此您大概想除以循环的最大迭代次数。

  2. 如果UI更新来自后台线程的速度快于UI无法处理的速度,则主线程可能会被更新请求积压(这使它看起来比实际速度慢得多)。为了解决这个问题,可以考虑使用调度源将“更新UI”任务与实际的后台更新过程分离。

可以使用DispatchSourceUserDataAdd(在Swift
2中dispatch_source_tDISPATCH_SOURCE_TYPE_DATA_ADD),在后台线程中根据需要频繁地发布add调用(dispatch_source_merge_data在Swift
2中为),UI会尽快处理它们,但在调用时将它们合并在一起datadispatch_source_get_data在Swift
2中),如果后台更新的传入速度比UI可以处理它们的速度更快。这样可以通过最佳的UI更新获得最大的后台性能,但是更重要的是,这可以确保UI不会成为瓶颈。

因此,首先声明一些变量以跟踪进度:

    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);
    }
}
2020-07-07