How can I convert to Swift async/await from GCD (DispatchQueue)? - swift

I am following Stanfords' CS193p Developing Apps for iOS online course.
It is using the Grand Central Dispatch (GCD) API for a demo of multithreading.
But they noted, that
"GCD has been mostly replaced by Swift's new built-in async API as of WWDC 2021".
So I wanted to learn how the code from the Lecture would look like after updating it to use this new API.
After watching Apple's WWDC videos, it seems to me like
DispatchQueue.global(qos: .userInitiated).async { } is replaced in this new async API with Task { } or Task(priority: .userInitiated) {}, but I'm not sure, what has DispatchQueue.main.async { } been replaced with?
So, my questions are:
Am I correctly assuming, that DispatchQueue.global(qos: .userInitiated).async { } has been replaced with Task(priority: .userInitiated) {}
What has DispatchQueue.main.async { } been replaced with?
Please help, I want to learn this new async-await API.
Here's the code from the Lecture, using old GCD API:
DispatchQueue.global(qos: .userInitiated).async {
let imageData = try? Data(contentsOf: url)
DispatchQueue.main.async { [weak self] in
if self?.emojiArt.background == EmojiArtModel.Background.url(url) {
self?.backgroundImageFetchStatus = .idle
if imageData != nil {
self?.backgroundImage = UIImage(data: imageData!)
}
// L12 note failure if we couldn't load background image
if self?.backgroundImage == nil {
self?.backgroundImageFetchStatus = .failed(url)
}
}
}
}
The whole function (in case you need to see more code):
private func fetchBackgroundImageDataIfNecessary() {
backgroundImage = nil
switch emojiArt.background {
case .url(let url):
// fetch the url
backgroundImageFetchStatus = .fetching
DispatchQueue.global(qos: .userInitiated).async {
let imageData = try? Data(contentsOf: url)
DispatchQueue.main.async { [weak self] in
if self?.emojiArt.background == EmojiArtModel.Background.url(url) {
self?.backgroundImageFetchStatus = .idle
if imageData != nil {
self?.backgroundImage = UIImage(data: imageData!)
}
// L12 note failure if we couldn't load background image
if self?.backgroundImage == nil {
self?.backgroundImageFetchStatus = .failed(url)
}
}
}
}
case .imageData(let data):
backgroundImage = UIImage(data: data)
case .blank:
break
}
}

If you really are going to do something slow and synchronous, Task.detached is a closer analog to GCD’s dispatching to a global queue. If you just use Task(priority: ...) { ... } you are leaving it to the discretion of the concurrency system to decide which thread to run it on. (And just because you specify a lower priority does not guarantee that it might not run on the main thread.)
For example:
func fetchAndUpdateUI(from url: URL) {
Task.detached { // or specify a priority with `Task.detached(priority: .background)`
let data = try Data(contentsOf: url)
let image = UIImage(data: data)
await self.updateUI(with: image)
}
}
And if you want to do the UI update on the main thread, rather than dispatching it back to the main queue, you would simply add the #MainActor modifier to the method that updates the UI:
#MainActor
func updateUI(with image: UIImage?) async {
imageView.image = image
}
That having been said, this is a pretty unusual pattern (doing the network request synchronously and creating a detached task to make sure you don't block the main thread). We would probably use URLSession’s new asynchronous data(from:delegate:) method to perform the request asynchronously. It offers better error handling, greater configurability, participates in structured concurrency, and is cancelable.
In short, rather than looking for one-to-one analogs for the old GCD patterns, use the concurrent API that Apple has provided where possible.
FWIW, in addition to the #MainActor pattern shown above (as a replacement for dispatching to the main queue), you can also do:
await MainActor.run {
…
}
That is roughly analogous to the dispatching to the main queue. In WWDC 2021 video Swift concurrency: Update a sample app, they say:
In Swift’s concurrency model, there is a global actor called the main actor that coordinates all operations on the main thread. We can replace our DispatchQueue.main.async with a call to MainActor’s run function. This takes a block of code to run on the MainActor. …
But he goes on to say:
I can annotate functions with #MainActor. And that will require that the caller switch to the main actor before this function is run. … Now that we've put this function on the main actor, we don’t, strictly speaking, need this MainActor.run anymore.

Related

Memory leak situation when storing a URLSession task in a property in Swift

I'm trying to understand the memory leak situation in Swift language but there is a situation that I'm still wondering.
I've created a new UIViewController and call fetch function with storing the fetch task in a property without starting the task then I closed this UIViewController.
I found that the deinit function in this UIViewController is not called (Memory leak).
func fetchAPI() {
let url = URL(string: "https://www.google.com")!
let task = URLSession.shared.downloadTask(with: url) { _, _, _ in
DispatchQueue.main.async {
print(self.view.description)
}
}
self.vcTask = task
}
But If I call the fetch function with calling resume method and then I close UIViewController again.
I found that the deinit function in this UIViewController is called (Memory not leak).
func fetchAPI() {
let url = URL(string: "https://www.google.com")!
let task = URLSession.shared.downloadTask(with: url) { _, _, _ in
DispatchQueue.main.async {
print(self.view.description)
}
}
self.vcTask = task
task.resume() // start downloading
}
For now I think that if I store a task in a property in UIViewController and I use self in the callback. It would create a cycle that caused Memory leak.
But when I call task.resume() Why the memory is not leak in this situation?
An un-resumed task will never execute its completion handler, because it will never complete. The task, and its handler, will therefore remain in memory.
We don't know the internal implementation of URLSession* but it would seem sensible for the framework to discard completion handlers once they are executed. This would break the retain cycle and allow the view controller to be deallocated.
You could confirm this by adding extra logging in the completion handler and deinit method - I would expect the view controller not to be deallocated until the completion handler has run.
(Adding to #jrturton's answer, which is 100% correct afaik)
This line of code
let task = URLSession.shared.downloadTask(with: url) { _, _, _ in ... }
captures self strongly, causing the memory leak.
One way to avoid this is to change the capture to be weak, like so:
let task = URLSession.shared.downloadTask(with: url) { [weak self] _, _, _ in
guard let self else { return }
DispatchQueue.main.async {
print(self.view.description)
}
}
Alternatively, try adding self.vcTask = nil to the ViewController's viewDidDisappear method to manually break the cycle.

Core data async fetch ends up on the main thread

I am trying to execute an asynchronous request as part of a search result updater in my app.
I wrote the following code
func updateSearchResults(for searchController: UISearchController) {
guard let text = searchController.searchBar.text else {return}
let threadingContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
threadingContext.parent = self.context
DispatchQueue.global(qos: .userInitiated).async {
let fetchRequest = MyObject.fetchRequest() as NSFetchRequest<MyObject>
fetchRequest.predicate = get_predicate(text)
do {
let objects = try threadingContext.fetch(fetchRequest).map({ object in
return object.objectID
})
}
catch {return}
DispatchQueue.main.async {
// Pass results to the search view controller
}
}
}
but the UI is still slow (even if I don't do any display update), and looking at the Time profiler, I see that my main thread is spending 80% of its time on the following:
So it seems that my request is still being dispatched onto the main thread, which I don't understand. Would anyone see my mistake?
(I tried a few various on the above e.g. using threadingContext.perform but for the same result)
Ok, I understood it, and I should have read Apple's documentation, but basically
If a context’s parent store is another managed object context, fetch and save operations are mediated by the parent context instead of a coordinator.
This is slightly subtle, but my construction would have been useful if the operations performed on the fetch request, rather than the fetch request itself, had been slow.
The solution is to set threadingContext.persistentStoreCoordinator instead.

Promises + Alamofire make sure network calls always on background

Does promises always run on background thread.
#IBAction func doNetworkCall(_ sender: Any) { // Does this run on
background thread
Network.fetchPhotos().done { (photos) in
}
}
static func fetchPhotos () -> Promise<[Photo]> {
return Promise { seal in
AF.request("https:photosURL", method: .post, parameters: ["auth":"1231","user_id":"u12312"]).responseJSON { (response) in
guard let data = response.data else { return }
let coder = JSONDecoder()
let photos = try! coder.decode([Photo].self, from: data)
seal.fulfill(photos)
}
}
}
I have used promises with purpose of all network calls runs on background thread irrespective calling from main thread. I have some chain network requests which will be easier to implement.
is this assumption correct?
Alamofire always runs its requests on a background queue. In your example the only part that isn't in the background is the responseJSON closure. By default that closure runs on the .main queue. I recommend you adopt responseDecodable to decode your responses so that the parsing is also in the background only call fulfill the promise in the closure. (I'm not sure whether fulfilling on the main queue is otherwise necessary.)

SWIFT - What's the difference between OperationQueue.main.addOperation and DispatchQueue.main.async?

Sometimes I must do something on the main thread and its suggested to place the code inside a OperationQueue.main.addOperation.
Other times, its suggested to write the code inside DispatchQueue.main.async.
What the difference between these two?
(There's a similar question title, but the content is mismatched.)
I used 'DispatchQueue.main.async' when I perform any task in main thread like Update my APP UI . When you need to run a further operation or block in main thread you can use 'OperationQueue'. Check this article to know more about OperationQueue
OperationQueue From Apple Doc
The NSOperationQueue class regulates the execution of a set of
Operation objects. After being added to a queue, an operation remains
in that queue until it is explicitly canceled or finishes executing
its task. Operations within the queue (but not yet executing) are
themselves organized according to priority levels and inter-operation
object dependencies and are executed accordingly. An application may
create multiple operation queues and submit operations to any of them.
Example :
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
activityIndicator.startAnimating()
calculate()
}
private func calculate() {
let queue = OperationQueue()
let blockOperation = BlockOperation {
var result = 0
for i in 1...1000000000 {
result += i
}
OperationQueue.main.addOperation {
self.activityIndicator.stopAnimating()
self.label.text = "\(result)"
self.label.isHidden = false
}
}
queue.addOperation(blockOperation)
}
}
DispatchQueue From Apple Doc
DispatchQueue manages the execution of work items. Each work item
submitted to a queue is processed on a pool of threads managed by the
system.
Example :
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
print(error ?? "Unknown error")
return
}
do {
let heroes = try JSONDecoder().decode([HeroStats].self, from: data)
DispatchQueue.main.async {
self.heroes = heroes
completed()
}
} catch let error {
print(error)
}
}.resume()
OperationQueue is just an objective C wrapper over Grand Central Dispatch (GCD / libdispatch).
If you are using OperationQueue, then you are implicitly using Grand Central Dispatch.
OperationQueue.main.addOperation therefore is using DispatchQueue.main.async under the hood. There is some overhead of using Objective C (OperationQueue) over a C API (GCD) so there is a slight performance gain for using GCD.
I recommend reading Brad Larson's answer on why he prefers GCD over OperationQueue but it is a debatable subject.
NSOperation vs Grand Central Dispatch.

Swift - Network Requests and Background Queue

just wanted some clarification on the best practices to make network api calls in Swift 2.
Here is how my typical network requests looks like to download JSON data:
let session = NSURLSession(configuration: .defaultSessionConfiguration())
let url = NSURL(string: my_url_string)
let request = NSURLRequest(URL: url)
let dataTask = session.dataTaskWithRequest(request) { data, response, error in
do {
self.tableData = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! [NSDictionary]
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
} catch let error {
print(error)
}
}
dataTask.resume()
My question is: should I wrap all of this code block into a background queue? Should I do as follows:
let download_queue = dispatch_queue_create("download", nil)
dispatch_async(download_queue) { () -> Void in
previous code here
}
Or should I use one of the given high priority queues such as:
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0)
Also, when making additional network requests on subsequent view controllers, should I use the same queue I use here or should I create a new one?
By default NSURLSession API is highly asynchronous. Usefull information from the Apple docs.
There is no visible issues that indicate to wrap you're code block with GCD and also completion block runs on background thread so there is right usage of the GCD to update UITableview