Urlsession Task Completes Once but Not Again

Swift 5.5 has a shiny new structured concurrency framework to help you write condom lawmaking more than swiftly. To assistance everyone get started, Apple tree provided a bunch of videos and sample code at WWDC 2021. There'due south a quick run-downwardly on what they cover at the cease of this tutorial.

The Twitterverse exploded and the usual actors (;]) have already published several how-tos. This tutorial is similar a micro version of Swift concurrency: Update a sample app from WWDC. You'll take baby steps to catechumen a much simpler app to acquire how async/wait and actors help you write safer code. To help you decipher Xcode'due south error messages and futurity-proof you against the inevitable future API changes, you'll explore what'south going on beneath the shiny surface.

Note: You'll need Xcode 13. This tutorial was written using beta 1. If you want to run this on an iOS device, it must be running iOS xv beta. For your Mac, Big Sur is OK. If y'all have a Mac [partition] running the Monterey beta, yous could try running your code in that location in case information technology's not working on Big Sur. You should be comfortable with using SwiftUI, Swift and Xcode to develop iOS apps.

Getting Started

Create a new Xcode project that uses SwiftUI interface and proper name it WaitForIt.

Create a new project named WaitForIt.

Create a new projection named WaitForIt.

In ContentView.swift, replace the torso contents with this code:

AsyncImage(url: URL(string: "https://files.betamax.raywenderlich.com/attachments/collections/194/e12e2e16-8e69-432c-9956-b0e40eb76660.png")) { prototype in   epitome.resizable() } placeholder: {   Color.scarlet } .frame(width: 128, height: 128)        

In Xcode thirteen beta 1, you become this fault:

Availability error

Availability error

Don't click whatever of the Prepare buttons! Go to the target folio and change Deployment Info from iOS xiv.0 to iOS 15.0:

Set Deployment Info to iOS 15.0.

Set up Deployment Info to iOS fifteen.0.

Get back to ContentView.swift. If the fault message is withal at that place, press Command-B to build the project.

Run Live Preview to come across the prototype for the "SwiftUI vs. UIKit" video:

AsyncImage

AsyncImage

OK, that was just a quick check to fix that Xcode glitch and also to show you SwiftUI's new AsyncImage view. Good, isn't it? :]

Earlier y'all get to work on the real WaitForIt app, take a high level look at how the new Swift concurrency fixes problems with the former GCD concurrency.

Old and New Concurrency

The erstwhile GCD concurrency has several problems that make it hard to write apps that safely use concurrency.

Swift concurrency provides the necessary tools to cleave piece of work upward into smaller tasks that tin run meantime. This lets tasks wait for each other to complete and allows you to finer manage the overall progress of a task.

Pyramid of Doom

Swift APIs similar URLSession are asynchronous. Methods automatically dispatch to a groundwork queue and immediately return control to the calling lawmaking. Methods take a completion handler and call delegate methods. Completion or delegate code that accesses UI elements must be dispatched to the chief queue.

If a completion handler calls another asynchronous function, and this function has a completion handler, information technology's hard to see the happy path in the resulting pyramid of doom. This makes information technology hard to check the lawmaking is correct. For case, this sample code from WWDC's Meet async/await in Swift downloads information, creates an image from the data, then renders a thumbnail from the image. Error handling is advertisement hoc considering completion handlers can't throw errors.

func fetchThumbnail(   for id: String,   completion: @escaping (UIImage?, Error?) -> Void ) {   permit request = thumbnailURLRequest(for: id)   let task = URLSession.shared     .dataTask(with: asking) { data, response, fault in     if permit error = mistake {       completion(nil, error)     } else if (response as? HTTPURLResponse)?.statusCode != 200 {       completion(nil, FetchError.badID)     } else {       guard let image = UIImage(data: information!) else {         completion(nil, FetchError.badImage)         return       }       image.prepareThumbnail(of: CGSize(width: forty, pinnacle: xl)) { thumbnail in         baby-sit let thumbnail = thumbnail else {           completion(nil, FetchError.badImage)           return         }         completion(thumbnail, nil)       }     }   }   task.resume() }        

The sequence of operations is much easier to encounter with async/await, and you can take advantage of Swift'south robust fault treatment mechanism:

func fetchThumbnail(for id: String) async throws -> UIImage {   allow request = thumbnailURLRequest(for: id)   permit (data, response) = try wait URLSession.shared.data(for: request)   baby-sit (response as? HTTPURLResponse)?.statusCode == 200 else {     throw FetchError.badID   }   let maybeImage = UIImage(data: data)   guard let thumbnail = await maybeImage?.thumbnail else {     throw FetchError.badImage   }   return thumbnail }        

Data Races

When multiple tasks can read or write an object's information, data races are possible. A data race occurs when one task sleeps while another task writes and exits, and so the sleeping task resumes and overwrites what the previous chore wrote. This creates inconsistent results.

In an app using the old concurrency, Xcode can detect data races if you lot enable the runtime Thread Sanitizer diagnostic in your app's Run scheme. Then yous tin can implement a serial queue to forbid concurrent access.

The new Swift concurrency model provides the Player protocol to prevent concurrent admission to an object'southward data. Actors likewise enable you to structure your app into code that runs on the principal thread and code that runs on background threads, so the compiler can help you prevent concurrent access.

Thread Explosion / Starvation

In GCD, the primary unit of work is a thread. If your code queues upwards a lot of read/write tasks on a serial queue, most of them must sleep while they wait. This means their threads are blocked, so the system creates more threads for the adjacent tasks. If each task likewise queues a completion handler on some other queue, that creates even more than threads. Every blocked thread holds onto a stack and kernel data structures so it can resume. A blocked thread may be holding resource that another thread needs, and so that thread blocks.

This is a thread explosion: The organization is overcommitted with many times more threads than it has cores to process them. The scheduler must allocate time to hundreds of threads, resulting in a lot of context switching. All of this slows downwards your app and can even starve some threads, so they never make any progress.

Tasks and Continuations

In Swift concurrency, the primary unit of work is a job. A task executes jobs sequentially. To achieve concurrency, a job tin can create child tasks. Or you tin can create tasks in a task group.

The system knows these tasks are related so it can manage deadlines, priority and counterfoil flags for all tasks in the task tree or grouping. This makes it easier for y'all to bank check and react to cancellation status, thus avoiding task leaks. If it'southward important to react immediately to cancellation, you tin write a function with a cancellation handler.

If a task suspends, information technology releases its thread and stores its state in a continuation. Threads switch between continuations instead of context switching.

Threads switch between continuations.

Threads switch between continuations.

The keyword await marks a suspension indicate, and an async frame on the heap stores information that information technology needs when it resumes.

Ideally, the number of threads never exceeds the number of cores. There is a cooperative thread pool and a runtime contract that every thread will make progress. Your lawmaking maintains this contract past using expect, actors and task groups to make dependencies visible to the compiler.

JokeService

Enough theory! Time to convert a simple download to utilise async/await.

The starter folder contains JokeService.swift. Add this file to WaitForIt.

JokeService is an ObservableObject that sends a request to an API that returns a random Chuck Norris joke. I've adapted this code from a sample app in Combine: Asynchronous Programming with Swift. The query item specifies the dev category, so all the jokes have a techie flavour. Alert: Some of these jokes are a niggling violent.

JokeService publishes a joke and its isFetching status. Its fetchJoke() method uses the standard URLSession.shared.dataTask with completion handler. If anything goes incorrect, information technology prints an error message with either the dataTask error or "Unknown error". If the latter, it provides no information on whether the problem was in the data or in the decoder.

Minimal Error Handling

Robust fault handling is 1 of the main reasons for async/await. The information task completion handler can't throw errors then, if information technology calls a throwing function like JSONDecoder().decode(_:from:), information technology has to handle whatever thrown errors.

It's mutual to have the easy way out and simply ignore the error. That'southward what the starter file does:

if let decodedResponse = try? JSONDecoder().decode(Joke.self, from: data)        

Previous Xcode versions advise this every bit a set up if y'all write just try and don't enclose it in a do/grab. Information technology means: Merely assign null if the role throws an error.

Delete ? to see what happens:

Translation: You can't throw from here!

Translation: Y'all tin can't throw from hither!

Xcode at present takes a harder line: No more than helpful suggestions of easy fixes.

But ? still works here, so put it back.

Bear witness Me a Joke!

To fetch a joke, open ContentView.swift and supplant the contents of ContentView with this:

@StateObject var jokeService = JokeService()  var torso: some View {   ZStack {     Text(jokeService.joke)       .multilineTextAlignment(.center)       .padding(.horizontal)     VStack {       Spacer()       Push button { jokeService.fetchJoke() } label: {         Text("Fetch a joke")           .padding(.bottom)           .opacity(jokeService.isFetching ? 0 : ane)           .overlay {             if jokeService.isFetching { ProgressView() }           }       }     }   } }        

Run Live Preview and tap the button. It has a nice effect with opacity and ProgressView() to bespeak a fetch is in progress.

A Chuck Norris joke

A Chuck Norris joke

Concurrent Binding

OK, the old way works, and then now yous'll convert it to the new fashion.

Comment out URLSession downwardly to and including .resume().

Add this code below isFetching = true:

async permit (data, response) = URLSession.shared.information(from: url)        

The new URLSession method data(from:) is asynchronous, so you use async let to assign its render value to the tuple (data, response). These are the same data and response that dataTask(with:) provides to its completion handler, merely data(from:) returns them straight to the calling office.

Where's the error that dataTask(with:) provides? Y'all'll detect out soon — wait for it! ;]

These errors and suggested fixes appear:

You can't call async from a non-async function.

You can't phone call async from a non-async function.

The errors are similar: You can't phone call an asynchronous part in a synchronous function. You accept to tell the compiler fetchJoke() is asynchronous.

Both fixes are the aforementioned, so click either 1. This gives you:

func fetchJoke() async {        

Like throws, the async keyword appears between the endmost parenthesis and the opening brace. You lot'll before long catch up with throws again.

Back to async allow: This is one way to assign the outcome of data(from:) to the (data, response) tuple. It's called a concurrent binding because the parent task continues execution subsequently creating a child task to run data(from:) on some other thread. The child task inherits its parent task'southward priority and local values. When the parent job needs to use data or response, it suspends itself (releases its thread) until the child chore completes.

The parent and child tasks run concurrently.

The parent and child tasks run concurrently.

Awaiting async

The verb for async is await in the same way the verb for throws is try. You try a throwing role and you lot wait an async part.

Add this line of lawmaking:

look (data, response)        

data(from:) throws.

data(from:) throws.

And there'due south the missing error that dataTask(with:) provides to its completion handler: data(from:) throws it. So you lot must try await:

try! wait (data, response)        

Note: The keywords must be in this order, non await effort.

Yous're not really going to utilise this code, then you don't bother to take hold of whatsoever thrown errors. This is merely a run a risk to see what happens.

What happens is surprising:

Immutable value may only be initialized once.

Immutable value may simply exist initialized once.

It'southward surprising because the Explore structured concurrency in Swift video says "And don't worry. Reading the value of event over again will not recompute its value."

Note: This seems to be a tuple bug. You can await data or response, but non both.

Get alee and accept the suggested fix to change let to var:

async can only be used with let declarations.

async can merely be used with permit declarations.

Hmph! Flashback to the early on days of learning how to use Swift optionals with Xcode constantly saying "You tin can't exercise that here". Perchance it'south a beta bug. It doesn't thing in this instance because there's no other code to execute between calling data(from:) and processing what it returns.

Sequential Bounden

Instead, you'll use the other bounden: sequential binding.

Supervene upon the two lines with this:

let (data, response) = endeavor? await URLSession.shared.data(from: url)        

Unlike async let, calling data(from:) this way doesn't create a kid task. It runs sequentially as a task in the fetchJoke() job. While it's waiting for the server response, this chore suspends itself, releasing the task's thread.

The task's data(from:) job suspends.

The chore'due south data(from:) chore suspends.

Only there'southward a problem:

Xcode refuses to understand try? here.

Xcode refuses to understand endeavor? here.

You try the lazy mode, simply Xcode won't have it this time, non fifty-fifty if you utilise nada coalescing to specify a aught tuple:

permit (data, response) =    endeavour? await URLSession.shared.data(from: url) ?? (zero, zilch)        

Nope, you'll have to practise the right thing. First, delete the ? and ?? (naught, nothing):

allow (information, response) = attempt await URLSession.shared.data(from: url)        

Error Handling Options

You have two options for handling errors thrown by data(from:). The offset is to bite the bullet and handle it right away with practise/catch:

practise {   let (data, response) = try await URLSession.shared.information(from: url) } catch {   print(error.localizedDescription) }        

The easier(?) option is to make fetchJoke() throw:

func fetchJoke() async throws {        

These keywords must announced in this guild — throws async doesn't work:

async must precede throws.

async must precede throws.

At present fetchJoke() only passes the error upwards to whatever calls fetchJoke(). That's the button in ContentView, where Xcode is already complaining about fetchJoke() beingness asynchronous:

fetchJoke() is async and throws: Do something!

fetchJoke() is async and throws: Do something!

Now what exercise you do? You tin't marking anything in ContentView equally async.

Creating an Unstructured Job

Fortunately, you can create an asynchronous task in the push button action. Replace Button { jokeService.fetchJoke() } label: { with this:

Push {   async {     try? await jokeService.fetchJoke()   } } label: {        

You create an asynchronous task with async { }. Considering it'due south asynchronous, you lot have to await its completion. Because information technology throws, you have to try to catch any errors. Xcode lets you use try? here, or you lot tin can write a exercise/catch statement.

Note: The task creation syntax will change to Task { ... } in a hereafter beta.

This is an unstructured task considering it's non part of a task tree. The async let task you created in fetchJokes() is a kid task of the task that's running fetchJokes(). A child task is bound to the scope of its parent job: The fetchJokes() task cannot terminate until its child tasks have finished.

An unstructured task inherits the player, priority and local values of its origin, but isn't bound by its scope. Cancelling its originating task doesn't bespeak the unstructured task, and the originating task tin stop even if the unstructured job has not finished.

Creating an unstructured chore in a not-asynchronous context feels just like DispatchQueue.global().async with a trivial less typing. But at that place's a big divergence: It runs on the MainActor thread with userInteractive priority, when the primary thread won't exist blocked.

You can specify a lower priority with asyncDetached:

Specify a priority for a detached task.

Specify a priority for a detached task.

But it will still run on the main thread. More about this later.

Decoding the Joke

Dorsum to JokeService.swift, to finish writing fetchJoke(). If you lot thought making it throw was the easier pick, encounter what you recall afterwards this section.

Because fetchJoke() throws, it passes any error thrown by data(from:) to the calling part. Y'all might every bit well have advantage of this mechanism and throw other errors that can happen.

Errors thrown by a throwing function must conform to the Error protocol, so add this code to a higher place the JokeService extension:

enum DownloadError: Mistake {   case statusNotOk   instance decoderError }        

You lot create an enumeration for possible errors fetchJoke() can throw.

So add this code to fetchJoke():

baby-sit    let httpResponse = response as? HTTPURLResponse,   httpResponse.statusCode == 200   // 1 else {   throw DownloadError.statusNotOk } guard let decodedResponse = try? JSONDecoder()   .decode(Joke.self, from: data) // 2 else { throw DownloadError.decoderError } joke = decodedResponse.value   // 3        

Using guard lets yous throw your specific errors to the calling function.

  1. You check the response condition lawmaking and throw statusNotOk if it isn't 200.
  2. You decode the response and throw decoderError if something goes wrong.
  3. You assign the decoded value to joke.

Note: You e'er have the option of catching an error, including those thrown by information(from:), instead of throwing it.

Now, where to set isFetching to simulated? This Published value controls the button'south ProgressView, and so yous want to ready it even if fetchJoke() throws an mistake. Throwing an mistake exits fetchJokes(), and then you still demand to set up isFetching in a defer statement, earlier whatsoever possible early on exit.

Add this line right below isFetching = true:

defer { isFetching = false }        

MainActor

If Xcode has trained you well, y'all might exist feeling a footling uneasy. Published values update SwiftUI views, so yous can't set Published values from a background thread. To set the Published values isFetching and joke, the dataTask(with:) completion handler dispatched to the main queue. But your new code doesn't carp to do this. Volition you get purple chief thread errors when you run the app?

Try it. Build and run in a simulator. Nope, no main thread errors. Why not?

Because you used async { } to create the fetchJoke() job in the button action, information technology's already running on the MainActor thread, with UI priority.

Actor is the Swift concurrency mechanism for making an object thread-rubber. Like Class, information technology's a named reference type. Its synchronization mechanism isolates its shared mutable land and guarantees no concurrent access to this state.

MainActor is a special Thespian that represents the main thread. You lot can think of information technology equally using simply DispatchQueue.main. SwiftUI views all run on the MainActor thread, and so does the unstructured task you created.

To see this, place a breakpoint anywhere in fetchJoke(). Build and run, and so tap the push button.

fetchJoke() is running on the main thread.

fetchJoke() is running on the main thread.

Yes, fetchJoke() is running on the primary thread.

What if you lower the priority? In ContentView.swift, in the button action, alter async { to this:

asyncDetached(priority: .default) {        

Notation: The syntax of this volition change to Task.detached in a future beta.

Build and run over again. Tap the button:

fetchJoke() is still running on the main thread.

fetchJoke() is still running on the main thread.

You lowered priority to default, but this doesn't movement the job to a background queue. The job notwithstanding runs on the main thread!

Notation: This seems to exist a fluke. The Explore structured concurrency in Swift video says a detached chore inherits nada from its origin, and then information technology shouldn't inherit the MainActor thread. A time to come Xcode beta might enforce this.

Alter the code back to async {.

To move the asynchronous work off the main thread, you lot demand to create an actor that isn't MainActor.

Histrion

Actors enable you to structure your app into actors on background threads and actors on the main thread, just as you now create model, view and view model files. Lawmaking in an histrion (lower case, not MainActor) runs on a background thread. So you just demand to motility the asynchronous role of fetchJoke() into a separate role player.

In JokeService.swift, delete the breakpoint and add together this code higher up JokeService:

individual thespian JokeServiceStore {   private var loadedJoke = Joke(value: "")      func load() async throws -> Joke {   } }        

You lot create an actor with a Joke variable and initialize it with an empty String, then write the stub of load(), where you'll motion the download lawmaking. This method resets loadedJoke and too returns the Joke, so you don't really demand a Joke belongings for this simple example, but yous probably will for more complex information.

Next, create a JokeServiceStore object in JokeService (in the class, not the extension):

private allow store = JokeServiceStore()        

Now motility the url code from JokeService into JokeServiceStore:

private var url: URL {   urlComponents.url! }  private var urlComponents: URLComponents {   var components = URLComponents()   components.scheme = "https"   components.host = "api.chucknorris.io"   components.path = "/jokes/random"   components.setQueryItems(with: ["category": "dev"])   return components }        

Then move the download code from fetchJoke() to load(), leaving only the two isFetching lines in fetchJoke():

// move this code from fetchJoke() to load() let (data, response) = try await URLSession.shared.data(from: url) guard    let httpResponse = response as? HTTPURLResponse,   httpResponse.statusCode == 200 else {   throw DownloadError.statusNotOk } baby-sit let decodedResponse = effort? JSONDecoder().decode(Joke.cocky, from: data) else { throw DownloadError.decoderError } joke = decodedResponse.value        

JokeServiceStore has a Joke property, not a Cord property, so supplant the last line with this code:

loadedJoke = decodedResponse return loadedJoke        

Instead of extracting only the value from decodedResponse, yous set the Joke belongings and also return this Joke example.

At present call load() in fetchJoke():

let loadedJoke = try expect store.load() joke = loadedJoke.value        

Build and run. Tap the button.

A joke appears, merely yous have purple warnings:

Publishing changes from background threads is not allowed.

Publishing changes from background threads is non allowed.

Add breakpoints inside load() and in fetchJoke() at isFetching = true, let loadedJoke = ... and joke = loadedJoke.value:

Set breakpoints.

Set up breakpoints.

Tap the button again, and then sentry the threads while you click Continue programme execution after each breakpoint:

fetchJoke() starts on the main thread but moves to background thread.

fetchJoke() starts on the principal thread but moves to groundwork thread.

The outset two lines of fetchJoke() run on the principal thread because a view calls it. Then load() runs on a background thread, equally it should. Only when execution returns to fetchJoke(), it'southward still on a background thread. Y'all demand to do something to make it run on the main thread.

@MainActor

Code that sets a Published value has to run on the MainActor thread. When fetchJoke() did all the piece of work, and you chosen it from Button in an unstructured task, fetchJoke() inherited MainActor from Button, and all of its code ran on the MainActor thread.

Now fetchJoke() calls load(), which runs on a background thread. fetchJoke() still starts on the main thread but, when load() finishes, fetchJoke() continues running on a background thread.

fetchJoke() doesn't have to rely on inheriting MainActor from Push. You can marking a class or a role with the @MainActor attribute to say that it must be executed on the MainActor thread.

Notation: If yous mark a class equally @MainActor, any calls from outside MainActor must look, even when calling a method that completes its piece of work immediately. A method that doesn't reference any mutable state tin can opt out of MainActor with the keyword nonisolated.

Add this line above func fetchJoke() throws {

@MainActor        

Build and run once again and click through the breakpoints:

fetchJoke() runs on the main thread after load() finishes.

fetchJoke() runs on the main thread after load() finishes.

The showtime three breakpoints are the aforementioned as before, but now fetchJoke() runs on the primary thread after load() finishes.

@MainActor fetchJoke() runs on main thread.

@MainActor fetchJoke() runs on main thread.

When fetchJoke() calls load(), it suspends itself, releasing the master thread to run UI jobs. When load finishes, fetchJoke() once again runs on the primary thread, where it's allowed to set the Published values.

Your work here is done! Endeavor converting your own SwiftUI projects: Take information technology wearisome, make small-scale changes and try to go on the app buildable afterward each alter.

I More Matter: Asynchronous View Modifiers

SwiftUI now has (at least) two view modifiers that await their action to call an asynchronous function.

Create a new SwiftUI View file named RefreshableView.swift and replace the contents of RefreshableView with this:

@StateObject var jokeService = JokeService()  var trunk: some View {   List {     Text("Chuck Norris Joke")       .font(.largeTitle)       .listRowSeparator(.subconscious)     Text(jokeService.joke)       .multilineTextAlignment(.center)       .lineLimit(nil)       .lineSpacing(five.0)       .padding()       .font(.title)   }   .job {     try? await jokeService.fetchJoke()   }   .refreshable {     endeavour? expect jokeService.fetchJoke()   } }        
  1. This view is a List considering refreshable(action:) just works with scrollable views.
  2. The task modifier performs its action when the view appears. Its activeness parameter's type is @escaping () async -> Void. It creates a job to run the activity and then you don't demand to.
  3. The refreshable modifier'southward action parameter type is the same. It must be asynchronous. When applied to a scrollable view, the user can pull down to refresh its content, and information technology displays a refresh indicator until the asynchronous task completes.

Run Live Preview. A joke appears:

A joke appears when the view loads.

A joke appears when the view loads.

Notation: Actually, this algorithm is O(N). ;]

Pull down to fetch another joke. You might have to pull down quite far.

If you want to run this version in a simulator or on a device, open up WaitForItApp.swift and alter ContentView() to RefreshableView() in the WindowGroup closure.

Where to Go From Here?

Download the concluding project using the Download Materials push at the pinnacle or bottom of the tutorial.

In this tutorial, you converted a simple SwiftUI app from the old GCD concurrency to the new Swift concurrency, using async/wait, throwing, an unstructured task, actor and @MainActor.

WWDC 2021 Videos

To acquire more virtually the fundamental concepts of Swift concurrency, watch these videos first:

  • Meet async/await in Swift: This session introduces endeavour look, become async, unstructured task and continuations.
  • Explore structured concurrency in Swift: This session covers concurrent and sequential binding, counterfoil, task groups, unstructured and detached tasks. There'due south a handy Flavors of chore table almost the end.
  • Protect mutable land with Swift actors: This session covers information races, actor, protocol conformance in extensions with nonisolated declarations, detached tasks, Sendable, conformance and MainActor.

These sessions are SwiftUI-specific:

  • Demystify SwiftUI: Go inspired to bank check all your SwiftUI code for inefficiencies. Lots of cute dog and cat photos.
  • Detect concurrency in SwiftUI: This session provides valuable tips for using structured concurrency in a SwiftUI app (SpacePhoto).

These talks encompass a few specific purposes:

  • Meet AsyncSequence
  • Apply async/await with URLSession: This session covers baby-sit permit and throw, upload and download methods, cancellation, AsyncSequence and AuthenticationDelegate. Lots of beautiful dog images.
  • Bring Core Data concurrency to Swift and SwiftUI

Prepare bated a good chunk of uninterrupted time for these deeper dives, or spotter them in x-15 minute chunks:

  • Swift concurrency: Update a sample app: This session is solid gilt! The presenter Ben Cohen is the Review Managing director of the Swift Evolution proposal SE-0304 Structured Concurrency.
  • Swift concurrency: Behind the scenes: This session goes in depth about thread explosion, the cooperative thread pool, continuations, async frames.

Melbourne Cocoaheads

And finally, two notable contributions from my colleagues at Melbourne Cocoaheads.

  • How to test Swift async/await lawmaking with XCTest by Giovanni Lodi. This is a companion slice to his presentation (starts at 46:32).
  • CombineAsyncually by Rob Amos. This is the companion repo for his presentation (right after Gio'south, at 1:17:27), where he demonstrates how you tin bridge the new async/wait functionality with Combine.

I hope y'all enjoyed this tutorial! Swift concurrency is a huge game-changer and it's still evolving. There'due south a lot to acquire, so accept some time at present to explore it, even if you tin't really utilise it for a few years.

WWDC is like Christmas...

WWDC is like Christmas…

If you accept any comments or questions, experience costless to join in the forum word below!

allenmearge.blogspot.com

Source: https://www.raywenderlich.com/25013447-async-await-in-swiftui

0 Response to "Urlsession Task Completes Once but Not Again"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel