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

Posted

技术标签:

【中文标题】在 Swift 中,如何在 GCD 主线程上调用带参数的方法?【英文标题】:In Swift how to call method with parameters on GCD main thread? 【发布时间】:2014-09-19 01:52:39 【问题描述】:

在我的应用程序中,我有一个函数可以创建一个 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”。我将如何做到这一点,或者有人可以建议一种将方法调用添加到应用程序主队列的最佳方法?

【问题讨论】:

【参考方案1】:

你可以切换到主线程使用

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
    

如果您使用任何架构并且只是想确保只有 viewcontroller 可以访问这段代码以在主线程上运行,然后实现您的扩展

extension UIViewController 
func runOnMain(code : @escaping()->()) 
    DispatchQueue.main.async 
        code()
    

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

【讨论】:

【参考方案2】:

如果你在闭包中使用 self,不要忘记弱化 self。

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

【讨论】:

您能解释一下我们为什么要这样做吗? 这是因为它可以创建内存循环——即我对某事有强烈的引用,而它对我有强烈的引用。这意味着我们都不能离开内存堆。【参考方案3】:

在主线程上重新加载collectionView

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

【讨论】:

【参考方案4】:

正确的方法是在 main_queue 中使用 dispatch_async,就像我在下面的代码中所做的那样

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

【讨论】:

【参考方案5】:

Swift 3+ & Swift 4 版本:

DispatchQueue.main.async 
    print("Hello")

Swift 3 和 Xcode 9.2:

dispatch_async_on_main_queue 
    print("Hello")

【讨论】:

【参考方案6】:

现代版本的 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 不够具体,但现在我们的 cmets 确实如此。感谢您指出这一点。【参考方案7】:
//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()
)

【讨论】:

【参考方案8】:

斯威夫特 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 的类型别名 - 一个接收 0 个参数且没有返回值的闭包,并且 block 是我们可以定义的函数的最后一个参数dispatch_async 的外部范围内的闭包。

【讨论】:

这正是我正在寻找的 3 行......现在你可以停止阅读我的想法了【参考方案9】:

这是更好的 (IMO) Swifty/Cocoa 样式语法,可实现与其他答案相同的结果:

NSOperationQueue.mainQueue().addOperationWithBlock(
    // Your code here
)

或者您可以使用流行的Async Swift library 来获得更少的代码和更多的功能:

Async.main 
    // Your code here

【讨论】:

方法重命名为OperationQueue.main.addOperation( 【参考方案10】:

这是一个不错的小全局函数,您可以添加一个更好的语法:

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

及用法

dispatch_on_main 
    // Do some UI stuff

【讨论】:

以上是关于在 Swift 中,如何在 GCD 主线程上调用带参数的方法?的主要内容,如果未能解决你的问题,请参考以下文章

GCD多线程在swift中的变化

swift GCD的简单使用

GCD主队列是不是必须在主线程上执行?

Swift系列三十一 - 多线程

GCD 全局队列任务在主线程上运行

Swift/iOS 子视图在主线程上未立即显示