In Swift how to call method with parameters on GCD main thread?
Asked Answered
A

11

212

In my app I have a function that makes an NSRURLSession and sends out an NSURLRequest using

sesh.dataTaskWithRequest(req, completionHandler: {(data, response, error)

In the completion block for this task, I need to do some computation that adds a UIImage to the calling viewcontroller. I have a func called

func displayQRCode(receiveAddr, withAmountInBTC:amountBTC)

that does the UIImage-adding computation. If I try to run the view-adding code inside of the completion block, Xcode throws an error saying that I can't use the layout engine while in a background process. So I found some code on SO that tries to queue a method on the main thread:

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

However, I don't know how to add the parameters "receiveAddr" and "amountBTC" to this function call. How would I do this, or can someone suggest an optimal way for adding a method call to the application's main queue?

Attribution answered 27/7, 2014 at 21:12 Comment(0)
R
524

Modern versions of Swift use DispatchQueue.main.async to dispatch to the main thread:

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

To dispatch after on the main queue, use:

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

Older versions of Swift used:

dispatch_async(dispatch_get_main_queue(), {
  let delegateObj = UIApplication.sharedApplication().delegate as YourAppDelegateClass
  delegateObj.addUIImage("yourstring")
})
Raynaraynah answered 27/7, 2014 at 21:19 Comment(1)
While you are correct that your suggestion works, I think my answer is slightly better because it does not make a call to UIApplication.sharedApplication, which is unusual and might throw off other readers of my code. The scope of my answer is constrained to the objects of importance, while yours brings in ancillary objects that require me to read more docs to learn exactly what I'm doing. And I've edited my original question to contain the correct function call. I had thought the displayQRCode wasn't specific enough but with our comments now it is. Thanks for pointing that out.Attribution
R
86

Swift 3+ & Swift 4 version:

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

Swift 3 and Xcode 9.2:

dispatch_async_on_main_queue {
    print("Hello")
}
Rosaliarosalie answered 16/6, 2016 at 10:34 Comment(0)
L
16

Swift 2

Using Trailing Closures this becomes:

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

Trailing Closures is Swift syntactic sugar that enables defining the closure outside of the function parameter scope. For more information see Trailing Closures in Swift 2.2 Programming Language Guide.

In dispatch_async case the API is func dispatch_async(queue: dispatch_queue_t, _ block: dispatch_block_t) since dispatch_block_t is type alias for () -> Void - A closure that receives 0 parameters and does not have a return value, and block being the last parameter of the function we can define the closure in the outer scope of dispatch_async.

Luann answered 6/3, 2016 at 20:51 Comment(1)
that was exactly the 3 lines i'm looking for ... now you can stop reading my mindPremise
T
8

Reload collectionView on Main Thread

DispatchQueue.main.async {
    self.collectionView.reloadData()
}
Teak answered 10/9, 2018 at 11:21 Comment(0)
E
7

Here's the nicer (IMO) Swifty/Cocoa style syntax to achieve the same result as the other answers:

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

Or you could grab the popular Async Swift library for even less code and more functionality:

Async.main {
    // Your code here
}
Effectuate answered 9/4, 2016 at 21:12 Comment(1)
method renamed to OperationQueue.main.addOperation({ }Freezedrying
S
5

you can switch to the main thread using

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

you can also write more reusable and readable code my writing you custom protocol by the following POP

protocol MainThreadRunAble : AnyObject {}

implement protocol using extension

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

conform your class to a protocol where you want to run on the main thread

class YourClass : BaseClass,MainThreadRunAble{}

then call one of the method based on your requirement

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

if you are using any architecture and just want to make sure, only viewcontroller has access to this piece of code to run on the main thread, then implement your extension

extension UIViewController {
func runOnMain(code : @escaping()->()) {
    DispatchQueue.main.async {
        code()
    }
}
func runOnMain(withDelay delay : Float ,code : @escaping()->()){
    DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
       code()
    }
}}
Solidus answered 19/11, 2021 at 18:13 Comment(0)
A
3

The proper way to do this is to use dispatch_async in the main_queue, as I did in the following code

dispatch_async(dispatch_get_main_queue(), {
    (self.delegate as TBGQRCodeViewController).displayQRCode(receiveAddr, withAmountInBTC:amountBTC)
})
Attribution answered 28/7, 2014 at 2:52 Comment(0)
S
2

Don't forget to weakify self if you are using self inside of the closure.

dispatch_async(dispatch_get_main_queue(),{ [weak self] () -> () in
    if let strongSelf = self {
        self?.doSomething()
    }
})
Sweitzer answered 15/2, 2016 at 15:25 Comment(2)
Could you please explain why we should be doing this ?Tatting
It's because it can create memory cycles - i.e. I have a strong reference to something and it has a strong reference to me. Meaning neither of us can leave the memory heap.Fertility
P
2

Here's a nice little global function you can add for a nicer syntax:

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

And usage

dispatch_on_main {
    // Do some UI stuff
}
Poach answered 26/4, 2016 at 11:58 Comment(0)
H
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()
})
Henna answered 22/12, 2018 at 6:47 Comment(0)
S
0

For modern Swift code (Swift 5.5+ and iOS 13+) Apple suggests main threading tasks to the Main Actor rather than GCD for cleaner, performant and safer code.

I've outlined 4 ways to dispatch to the main thread using actors in detail here.

The simplest possible would be to annotate the method in question with the @MainActor property wrapper.

@MainActor func callFunctionOnMainThread(paramOne: Int, paramTwo: String) {
    // We can now access parameters on the main thread
}

We make use of Structured Concurrency i.e. async/await:

await callFunctionOnMainThread(paramOne: 2, paramTwo: "Two")
Sabelle answered 4/6, 2022 at 1:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.