在Swift中如何在GCD主线程上调用带参数的方法?

212

在我的应用程序中,我有一个函数,它创建了一个NSRURLSession并使用它发送了一个NSURLRequest。

sesh.dataTaskWithRequest(req, completionHandler: {(data, response, error)
在这个任务的完成块中,我需要进行一些计算,将UIImage添加到调用的视图控制器中。我有一个名为的函数
func displayQRCode(receiveAddr, withAmountInBTC:amountBTC)

这个程序负责添加UIImage图片。如果我试图在完成块中运行视图添加代码,Xcode会抛出一个错误,说我不能在后台进程中使用布局引擎。因此,我在SO上找到了一些代码,尝试在主线程上排队一个方法:

let time = dispatch_time(DISPATCH_TIME_NOW, Int64(0.0 * Double(NSEC_PER_MSEC)))

dispatch_after(time, dispatch_get_main_queue(), {
    let returned = UIApplication.sharedApplication().sendAction("displayQRCode:", to: self.delegate, from: self, forEvent: nil)
})

然而,我不知道如何添加参数"receiveAddr"和"amountBTC"到这个函数调用中。我该怎么做呢?或者有人能建议一种最佳的方法来添加一个方法调用到应用程序的主队列吗?

11个回答

524

现代版本的Swift使用DispatchQueue.main.async来调度到主线程:

DispatchQueue.main.async { 
  // your code here
}

要在主队列上延迟执行,请使用以下方法:

DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
  // your code here
}

旧版本的Swift使用了:

dispatch_async(dispatch_get_main_queue(), {
  let delegateObj = UIApplication.sharedApplication().delegate as YourAppDelegateClass
  delegateObj.addUIImage("yourstring")
})

虽然你的建议是正确的,但我认为我的答案稍微好一些,因为它不会调用UIApplication.sharedApplication,这是不寻常的,可能会让其他读者对我的代码感到困惑。我的答案范围仅限于重要对象,而你的答案引入了需要我阅读更多文档才能学习到确切操作的辅助对象。我已经编辑了原始问题以包含正确的函数调用。我曾认为displayQRCode不够具体,但现在通过我们的评论,它已经足够了。感谢你指出这一点。 - almel

86

Swift 3+ 和 Swift 4 版本:

DispatchQueue.main.async {
    print("Hello")
}

Swift 3 和 Xcode 9.2:

dispatch_async_on_main_queue {
    print("Hello")
}

16

Swift 2

使用尾随闭包,代码变为:

dispatch_async(dispatch_get_main_queue()) {
    self.tableView.reloadData()
}

Trailing Closures是Swift的一种语法糖,它使得我们可以在函数参数范围之外定义闭包。更多信息请参见Swift 2.2编程语言指南中的Trailing Closures。

在dispatch_async情况下,API为func dispatch_async(queue: dispatch_queue_t, _ block: dispatch_block_t),由于dispatch_block_t() -> Void的类型别名(一个不接受任何参数且没有返回值的闭包),而且block参数位于函数的最后一个位置,因此我们可以在dispatch_async的外部定义闭包。


1
这正是我正在寻找的三行代码...现在你可以停止读我的思维了。 - Laszlo

8

在主线程上重新加载collectionView

DispatchQueue.main.async {
    self.collectionView.reloadData()
}

7
以下是我认为更好的Swifty/Cocoa风格语法,用于实现与其他答案相同的结果:
NSOperationQueue.mainQueue().addOperationWithBlock({
    // Your code here
})

或者您可以使用流行的Async Swift库,以少量代码获得更多功能:

Async.main {
    // Your code here
}

方法重命名为 OperationQueue.main.addOperation({ }) - Frostmourne

5

你可以使用以下代码切换到主线程:

DispatchQueue.main.async {
        // UI Code Goes Here
    }

通过以下POP,您可以编写自定义协议,从而编写更可重用和可读性更好的代码。

protocol MainThreadRunAble : AnyObject {}

使用扩展实现协议

extension MainThreadRunAble {
func runOnMain(code : @escaping()->()) {
    DispatchQueue.main.async {
        code()
    }
}
func runOnMain(withDelay delay : Float ,code : @escaping()->()){
    DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
       code()
    }
}}

将您的类符合所需运行在主线程的协议

class YourClass : BaseClass,MainThreadRunAble{}

然后根据您的需求调用其中一种方法

runOnMain {
        //update on main
    }
    runOnMain(withDelay: 1) {
    //update on main
    }

如果您正在使用任何架构,并且只想确保只有视图控制器可以访问此段代码以在主线程上运行,则实现您的扩展。

extension UIViewController {
func runOnMain(code : @escaping()->()) {
    DispatchQueue.main.async {
        code()
    }
}
func runOnMain(withDelay delay : Float ,code : @escaping()->()){
    DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
       code()
    }
}}

3

正确的做法是在主队列中使用dispatch_async,就像我在下面的代码中所做的那样。

dispatch_async(dispatch_get_main_queue(), {
    (self.delegate as TBGQRCodeViewController).displayQRCode(receiveAddr, withAmountInBTC:amountBTC)
})

2

如果您在闭包内部使用 self,请不要忘记使其弱引用 weakify。

dispatch_async(dispatch_get_main_queue(),{ [weak self] () -> () in
    if let strongSelf = self {
        self?.doSomething()
    }
})

1
请问您能否解释一下为什么我们应该这样做? - Jackspicer
这是因为它可以创建内存循环 - 即我对某个东西有强引用,而它对我也有强引用。这意味着我们都无法离开内存堆。 - jackofallcode

2

以下是一个不错的全局函数,可以让语法更加优雅:

func dispatch_on_main(block: dispatch_block_t) {
    dispatch_async(dispatch_get_main_queue(), block)
}

使用方法

dispatch_on_main {
    // Do some UI stuff
}

2
//Perform some task and update UI immediately.
DispatchQueue.global(qos: .userInitiated).async {  
    // Call your function here
    DispatchQueue.main.async {  
        // Update UI
        self.tableView.reloadData()  
    }
}

//To call or execute function after some time
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
    //Here call your function
}

//If you want to do changes in UI use this
DispatchQueue.main.async(execute: {
    //Update UI
    self.tableView.reloadData()
})

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接