Getting Started with Swift Concurrency (Async Await, Actors, Continuations, Tasks)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

Great video. Thanks for posting this

👍︎︎ 1 👤︎︎ u/kylemusco 📅︎︎ Nov 15 2021 🗫︎ replies
Captions
hey and welcome back to another video and in today's video actually going to explore a existing topic but it actually has new updates to make our life easier swift concurrency so in this video what we're going to do is you're actually going to look at what the new updates are to swift concurrency and how we can use these new updates in our code with practical examples as well as discussing the benefits of using these new frameworks and features that we get with swift i'm going to link the startup project in the description so you can follow along at your own pace so as you can see on the screen here there's actually quite a few topics that we're going to cover and with these topics we're going to discuss the benefits why we need to use this why this was introduced and also how we can incorporate this into our code so without further ado let's get started so before we actually get started and actually look and dive straight into the code i want to actually talk about what is concurrency so if you're new to this you may be wondering what is this brother talking about so essentially concurrency is is it allows us to basically execute code in parallel so we can execute multiple tasks at the same time so within swift concurrency there's also a concept as well or just in concurrency in general there's also a concept as well called a synchronous programming so what is asynchronous programming as well well if you ever work with an app that needs to interact with a service or a website and you've had to wait for that response that is asynchronous code it's essentially code that you basically perform in action and you wait for it to give you some kind of result similar to how you basically order something on amazon and you wait for it to be delivered to your house but what i actually want to do is i want to break down these concepts within concurrency and talk about what they are a bit more in detail so the first one we're going to tackle is synchronous so what is synchronous so synchronous is when you basically wait for a task or operation to finish so let's say you have a function called get full name well what you're doing is you're actually calling telling the function to give you something back so the program will basically execute that code and return the result you don't really need to wait for it it'll just happen instantly so next to uh synchronous programming you also have asynchronous and this is what i was talking about a second ago where you essentially execute some kind of task it does some work somewhere and then you wait for the result to come back so like i said before just imagine as if you're ordering something from amazon and you're waiting two to four days for you to receive that parcel in that unnecessary big box that you don't really need and then after these two you also have something called parallel so parallel is essentially when you have different tasks which you are going to execute on multiple threads so when you're actually working with parallel code you can actually execute code that is actually synchronous and asynchronous now we'll go go and discuss the benefits of this when you get to a topic of when you actually want to run your code in parallel and the benefits of doing so it's really important to actually understand all these concepts because the new async await which is being offered to us by swift is actually built on top of these base state programming concepts so what we're actually going to do now is we're actually going to jump into our first practical example and actually explore async away and how it actually ties in with these topics that we've just spoken about so let's jump in and do that now okay cool so we're actually going to get started now um but before we actually dive straight into the swift um async await code i just want to talk about the actual startup project structure so you can understand like what's going on and you're not thinking what is this brother trying to do so essentially what we've got is we've got a bunch of xcode projects and we also have a workspace so what you want to do is you actually want to click on the workspace file not the individual folders so once you click on this what this will do is i'll actually open up all of the um xcode projects that i've linked to this workspace okay cool and what you'll see now is you'll see a bunch of xcode projects here which is exactly what you should be seeing and you'll also notice as well if you go to a target at the top you'll see a list of all the projects as targets now what you need to make sure you do is when we actually look at a practical example you need to make sure that your the right scheme is selected and also that you're in the right xcode project so the first we're going to start off with is async await just this one here so let's just go into here okay cool so if you actually just open up the playground sample you'll notice inside that we basically have a model errors and we have a to do service which is simulating fetching and to-do's from this json placeholder api now if you're someone who's worked with networking before a lot of this code will actually not look any different to you it's something that you've probably written um a hundred times if it is new to you then essentially what we're just doing here is calling an api to fetch some data to display in our app if we wanted to so looking at this piece of code here this is actually not synchronous this is actually asynchronous and remember what i said to you before the reason why it is is because we're actually calling a service to fetch some results and when that task finishes it will then return it to us so it's not something that happens instantly so if you actually go down here you'll see that we use the um to do service we have a fetch function which has a closure which gives us the results and within our to do service implementation we're also using a completion handler as well to handle whether there was a success or if there was an error now looking at this you may think that this is actually okay and there's nothing wrong with this but when you actually look at it there actually is a few things that you know is a bit nasty about this and we could improve so let's discuss this now well the first thing is is what about if we actually look at this implementation here it's actually a lot of code and it's actually a lot to read and a lot to follow on a lot to track it's not very nice and readable to read the second thing is is i don't know if you clocked it but it's actually very easy as well for us to forget to actually return an error in all case pass now in this example i've actually handled all the possible places where it could actually fail but if you actually look at it because of how you know messy the code is it's actually easy to forget to do something like this and then because you forgot to do something like this now you actually would not return an error so if this line ever got read then essentially nothing would happen also finally as well is what about in the future if i actually decide that you know what i need to actually chain another function on top of this function well you'll get introduced to something called callback hill what is that so call back hell is essentially when you do stuff like this so let's say inside the fetch the success i want to call another um asynchronous task to execute some to execute and get something from a service i could do this now you're looking at this you might be thinking that like well you know it's only two functions it's actually not that bad to just nest it like this but what about if we had to chain three or four to handle like authorization or something this can get a bit messy in order to resolve all these issues that i've just raised we can actually use a new capability that's only available as of now because it's not being um available for to backport to previous version of ios but as of right now from ios 15 we can actually use a new set of features called async away now what is async await in swift so to better explain async away what i actually want to do is actually want to use a graphic example just to go through what each bit means and then what we'll do is we'll actually apply these concepts to our example before and actually refactor it using this new stuff so with async away let's assume that we have a function called fetch characters and what this function will do is it will basically you know interact with a service and return us an array of characters well with async await what we can actually do is we can actually call a new function and that we get with the url session and what this will actually do is it will actually suspend the function and await its value so there's two things that we need to actually break down here so the first thing is suspend so what do i mean by suspend so when the system suspends a function you're basically saying to the system that i want you to execute this task or do it when you're ready so the system will decide the best time to actually execute the task that you've set off for it and when the system is ready you'll execute that and return the result to you so because you're doing this the system basically decides when it's best to actually execute a task so you can actually execute other tasks and get the most out of your application so when your system actually um wants to suspend you actually mark that by saying with the asynchronous function that you want it to await so by you saying that you want to await this value that's you essentially telling the system that this is the suspension point so after the system has finished the spending and it now resumes and gives you back your value what we'll actually do is actually resume and once it resumes it will actually give you back the value that you're expecting from the asynchronous task now it's also worth noting that sometimes you may not even realize that the system is suspended and the reason why that is because the system may decide that i actually don't need to actually suspend because it's not that many tasks but if you're actually in an application that runs a lot of heavy tasks that need to do stuff i.e you're building like a drawing app and you actually need to cache and some data to some sort of local storage then maybe the system will decide actually you know what yeah let's do this test first before we actually complete the test from before so after you finish resuming what you will then get is your result and then you can actually handle your result from there now it's worth noting that the um syntax for this is actually a bit different to using completion handlers and that's what we're actually going to do now and actually discuss this concept that we've spoken about in our project example so let's do that now so the first thing i'm actually going to do is i'm actually just going to comment out this piece of code here the fetch because we don't want to use that and i'm also going to actually rather than comment out let's delete it so i'm actually going to delete it and what i'm also going to do is i'm actually going to also delete this fetch here because it doesn't exist anymore quite cool so what i'm just going to do is i'm just going to write out the basic signature for and writing a asynchronous function using async away and then we'll break down what actually means so let's do that now okay cool so if we actually look at our code here and compared to before with completion handlers we don't actually have any more completions anymore instead this time we actually have a return type so we actually have a function here called fetch which is an asynchronous function so we use the async keyword to market to say to the compiler that this function is asynchronous and then what we say here is we want to return an array of to-do's now as of right now i've just said that i want to return an empty array but we'll actually get to the next part where we actually execute our api request to actually fetch the data from the service now there's one thing i want to talk about here because we actually match our functioning as asynchronous as well and we're also going to be fetching data from some kind of external resource it's worth noting that this code will actually not be executed on the main thread automatically this will actually be executed on a background thread so what we're going to do now is actually write out the rest of the code to actually execute this api request asynchronously so let's look what we've got here so we've defined our url in our function now i wouldn't normally do this but just for the sake of this simple example we're just going to do it like this and then what we've got here is we've now got a function here url session share.data and we've got two new keywords here so we've actually got try and await now i'm going to break down the try in a second and then we'll talk about um the away as well so what we're saying here with the away is that this is our suspension point so this function here url session shared that data is actually a new asynchronous function that we actually get from the foundation and framework so it's worth noting that apple has actually updated a lot of their functions that use completion handlers previously to automatically opt in to now using async await with swift concurrency so compared to the other version of this that you saw with a completion handle this is the asynchron async await version of that very same function so what we're saying here is this is the suspension point for where we want to await the value from the service now we also use the try keyword as well because what will actually happen is if something goes wrong or if something fails what we'll actually do is we'll actually get an error thrown back towards from the url session and function and then if you actually look on the left hand side here in the data this is our data that we received back from the request that we executed and this is the response that we actually received back from the request that we executed as well now it's also worth noting as well that we use the try keyword here as well before the await so what does this actually mean so with asynchronous functions they can actually also throw as well so by rows if you want to actually throw an error with this function you can actually do that as well now it's quite easy to add this capability all you need to do is just type the keyword roles after async that's it quite simple right so the next thing that we need to do is we actually need to handle our response from our service and actually handle decoding the data as well and also checking as well that the response that we get back from the service is a valid response so let's look into doing that now now let's tackle this from top to bottom so we've got our response and we're checking if we can actually cast it as a http url session and also as well we're checking the status code is 200 now if this doesn't pass you'll notice here that we actually throw an error now remember before how i said that with completion handlers it's very easy for you to miss uh completion in terms of when something is successful or when there was a failure can't really do that here because we've marked the function as froze and also as well when you actually use a guard statement it's kind of expecting you to return something as well so here we're saying that we want to throw an error to say that this was actually an invalid request now if this is all successful what we'll do is we'll then go on to the next step which here we're using the json decoder to try to decode the response that we get back from the service into an array of to-do lists now again because if you try here if this was to ever mess up we automatically will get the function throwing an error table so we can handle that error and then if this all passes what we'll do is we'll actually return the decoded data now this was quite um a bit um to discuss them but if you actually look at the code and the lines that we've just written as well in terms of readability also this in my opinion is a lot more readable to you know me now if i was to ever have to write code which involved me interacting with this service on api and i only need to use ios 15 then to me it makes sense to use something like this because it's easy to read and it's better to maintain and we'll talk about how we can scale this up in our next examples so the next thing i want to do is talk about how can we actually even use this fetch function because we wrote it out but we don't know how to use it well what we need to do is we actually need to make sure that our asynchronous code runs within a task but just to show you just to showcase how this works what i'm going to do is i'm going to type this out and wrong and show you what's discussed what's wrong with it and then we'll basically build it up until it it works fine and it's right so let's do that now okay cool so make sure that the target selected the scheme selected is async await and also as well you'll notice that when we actually tap this out we get an error saying that async call in a function not support concurrency so what are we getting here so essentially this is the compiler telling us that we're actually trying to execute an asynchronous function that's not within some kind of task a concurrent task that is as well so in order to fix that what we'd actually need to do is actually use this within a task so to build a task it's quite simple all you need to do is just type out the keyword task a open and from c open curly braces close for the braces and then now we've got it in a task now you'll notice if we actually build this again we're not finished just here we actually also get another error saying that the call can throw but it's not marked with a try and the reason why we're getting this because remember what i said to before this function actually throws an arrow so what we need to do is we actually need to mark this with a try and then finally the last error we get is we're saying that the expression is async but we need to mark it with a weight and the reason why we need to mount this with a weight is because remember i said this is an asynchronous function so we actually want this function to run asynchronously and by marking with a weight we're telling the system that this is a suspension point for you to execute a task and when you finish with that task give us back the result so all we need to do is use a keyword a weight like so and if you just build this now you should notice that we actually don't get any more errors so what i'm going to do is i'm just going to dump out the to-do's into the console and then what we'll do as well is in the cache statement we'll actually print out any errors so we'll just print out the errors so what we need to do now is we actually need to execute and actually run this um task so in the playground if you just hit run cool so when we get a response back you'll see that we've actually successfully called it that to do service and we have a list of all the possible to do's from that response well we've got 200 of them and as you can see compared to before now we've wrote a function where we've marked it we tried a weight we fetched it to these and we just dumped it out so in terms of readability and in terms of the um code i was handling anything that's gone wrong with errors and whatnot this is a lot more and secure than using completion handlers in my opinion so now we spoke about async away and how we can use it to actually improve our code but what i want to do is actually want to talk about structured concurrency specifically within swift and how it relates to how tasks get executed and also as well this goes what even is a task because you saw me mention here that we need to create a taskbar i never went into too much detail in terms of what this specifically is so let's do that next okay cool so before we actually jump into what tasks are let's look at structured concurrency so you may be wondering what has this guy been saying about structured concurrency it sounds like it's his favorite word well it's actually a very um important concept within you know swiping currency so frequent concurrency allows us to basically find a sequence of asynchronous tasks that we want to actually execute you know one after the other so remember before when i said to you that like what about if you're in a situation where you have to chain like one to five tasks in a row well this is what structured concurrency allows us to do it allows us to execute multiple tasks one after the other asynchronously so what we're actually going to do now is we're actually going to look at an example of this and how we can actually use this to chain and you know request one after the other sequentially so let's do that now so in your xcode start project if you go into swift concurrency and then you go into the sample and then if you make sure that your scheme is selected structured concurrency here we look at this file you'll notice that we have two models so we have user and pulse so what we're actually doing here is we're actually simulating getting the uh posts and also getting the users at the same time so one running one after the other so if we actually look at our class here if we scroll down we have a generic function called fetch which allows us to fetch a response from a service using generics and then within our function called fetch content the first we're doing is we're going to fetch the users and then after fetching the users the next one we're going to do is we're then going to fetch the posts okay cool and if you scroll down here you'll notice that we get the um data for the users first and then we get the data for the pulse but remember what i mentioned to you before about that thing called um callback hell now looking at this if you look at it here it's quite messy and even for me now i don't really clearly you know see what's actually going on it might take me a while to actually figure out what is what is i'm actually even trying to do here so what we're going to do is we're actually going to look at how we can use structure concurrency to actually execute these two requests one after the other sequentially and also clean up the code here so let's look at how we can do that now the first thing that we're going to do in order to clean this up is we're actually going to refactor our fetch function here and rather than being a completion handler we're actually going to refactor this to use async away and return to us the um value for the url and the type by tracity code so let's do that now so in order to do that let's just delete this completion handler here first so the first one we're saying here is we're going to instead of just using a completion handler we're going to use the async keyword and it's going to froze and it's going to return us of type t so t is our generic that we define here which is of which is a codable now the next thing that we need to do is we need to actually refactor this whole function and rather than using the completion handler with url shared session we're now going to use the previous example from our async await chapter before to return and decode t so let's do that now so i'm not going to too much detail but if you just go into it line by line we're using the url sessions new async await function to await the result from the url and we get a response and check if it's okay if it's not we throw an error and we try to decode the date or if that works and it's fine then we continue if it's not then we throw some kind of error and then we just return the decoded data that we get back from the service so pretty straightforward now we've got our generic function that we can use to execute a synchronous code we can now actually use this to actually easily change our you know easily chain our request to get the post and the users so if we actually scroll up now here what we need to do is everything inside of this fetch content function we actually now need to actually delete it okay cool so now we delete the contents of that so the next thing we want to do is actually delete this completion handler i'm going to delete this and i'm going to scroll to the top here so we actually had this content handler here which was a type failure so it's just easier to understand its purpose but because we don't need this no more i'm also going to delete this now instead of us using that we're actually going to use this type alias here and what this type alias is is it's basically just a tuple which has the users and the um posts so let's make that better so let's say post rather than post because it's more than one so what this function is going to do is it's actually going to return the content and it's also going to throw as well if something goes wrong so now let's update this uh function signature okay cool so we said that this function is asynchronous it froze and it returns the content which is this cheaper here all right sweet so the next bin that we actually need to do is we actually need to use um our fetch function from below here to actually now fetch our users so let's do that now okay cool and as you can see here we've just marked it with try a weight and this will actually fetch the users from this endpoint and try to decode it to this array of users as you can see here now after we finish fetching the users the next thing that we want to do is we actually want to fetch the pulse so let's do that now and you can see here similarly to users we're now just doing the exact same thing except this time the only thing that's changed is the url string so as you can see here what's going to happen is we're actually going to tell the system to try and fetch the users and at this point we mark this as a suspension point so it will actually move this task to the system and say execute this when you're ready and then once this has given us a result and resumed the next thing it will do is they'll actually try to then fetch the posts as well so once we get the user's back it will then try to fetch the post and then once we get a post and we have both of these values set we're then going to return them in our tuple here content so we actually need to return that here now so let's do that and if you actually look at the signature for content it literally is just a two-point just passing the users and the post which is why this is valid so what we want to do now is we actually want to go down here and actually fix our average that we have here so because we don't have a completion handler anymore obviously this is not going to work so what we actually need to do now is similar to what we did before we need to execute our function within a task and we're going to print out the users and the posts so let's do that now okay cool so this isn't any different to what we um had before except now obviously we're just using um a task and we're catching any errors so what we're going to do is we're going to execute this and run it and see what happens and as you can see what's happened is if we just scroll all the way to the top you'll notice that the first thing that was executed was our users so we've got a list of our users and you can see here here's the end of the users and then after the system resumed it then started to actually execute our request to retrieve our posts but as you can see when you're actually working with async away and you actually need to chain and you know tasks one after the other this is actually very very easy to do and you can see here is really readable and even if we wanted to we could actually pass get values from our um constant here users and actually pass it into a function and do all sorts of stuff so i'd highly recommend you to chain request to look into using async away if possible so the next thing we're going to look into now and actually discuss the breakdown is tasks so let's do that now so we've spoken about unstructured concurrency now let's talk about tasks so what are tasks so it's a new feature in swift that allows us to actually run our asynchronous code pass also provides you with a concurrent context so you can actually execute this asynchronous code so when you actually want to execute an asynchronous function it needs to be within a task and the reason why that is because you may want to run this um task async personally or you may want to run this task um concurrently with other tasks as well so within swift so let's say for example if you need to actually send off a you know bunch of network requests to a service or if you need to cache heavy objects you can actually run tasks in parallel to actually allow you to do this as well now within tasks there's actually different types of tasks that you can use and we're going to explore each one here so some of the tasks include async and group tasks unstructured tasks and detach tests as well so what we're going to do now is actually want to break down the different types of tasks and talk about the benefits and use cases of when you want to use each one so the first one i want to look at is async let tasks so an async let task just to give you a summary before we dive deeper is essentially a task that you can actually group together and execute in parallel so with async left tasks what you can do is you can actually mark them multitask with the async keyword to let the compiler know that this is a asynch task that you want to execute also as well when using async left task it actually allows you to execute child tasks within a main task then you can await the results what will happen is once you await the result of this child task it will actually you know mark it as finish once all the child tasks have finished executing so this is a good way is if you're ever in a situation where you actually need to run multiple things in parallel and then actually collect them all together you probably may want to look at using an async task because it helps serve that purpose now if you're running something which is quite dynamic i if you're working with an array of objects and you're not too sure how many tasks you need to actually execute then there's another purpose for this which i'll get to but if you have a fixed amount of tasks that you need to execute i you need to get the user you need to get their polls and you should also get their profile picture from three different endpoints then it makes sense probably to use an async task what we're actually going to do now is actually look at this with a code example and actually see the benefits of using an async the previous laptop or structured concurrency where we waited for one task to finish before we start the next one so let's do that now oh so we go to async left and go into the view controller and make sure that your scheme is set to async let you'll notice inside of this view controller we actually have some code again to just simply execute and fetch to do's this time we're not actually fetching an array of to-do's or actually fetching to-do's based on their id so what i want to do is i actually want to show you what we have here within the video load we actually have a task here and what it's going to do is it's going to execute and fetch free to do's one after the other so you can see here we fetch the first second and third we get the time as to when we started the request and when it finishes and then we compare how long that took in nanoseconds so if we actually just run this now we'll see what happens so i'm not showing you the simulator because there's actually nothing to show you but you can see here that i actually run in one five one nine four three or one seven five that's long nano seconds so this is actually still quite quick but how can we actually make this quicker well remember what i said to you before with async it actually allows us to execute tasks in parallel and when all the child tasks have finished then we get our results so raven was actually waiting for this to finish and then resume this appears resume distribution resume what about we actually just execute all three of these at the same time on multiple threads well we can do that with async left so what we're going to do is actually look at how we can actually write this using async let and we'll actually compare the time it takes to actually execute that so let's do that now okay cool so what we've done here now is we have async let's start which is a constant that holds when we start our async let task and if you notice here brown is using try away instead we're actually using the keyword async on each one so this is also marking and telling the compiler that this is actually something that we want to execute in its own child task and we will actually await the value for when it finishes so we actually scroll down here as well you'll actually notice that we actually marked this with try await and we have the async child task within the array for when we actually want to wait for this to finish finally what we do is we get when this task when this has finished and then we compare the time for when it took falls we compared the time for how long it took our a single left task execute and then we just basically checked to see which one was quicker out of async let an async await so what we're going to do now is we're going to actually execute this and we're going to compare if it's quicker or not so let's just run this and see what happens so as you can see the asynclet actually executed a lot quicker than the async await and like i said the reason why that is again is because we're not waiting and suspending the system we're essentially executing all three of these tasks in parallel so because we're using doing that in parallel and using more threads this is automatically going to be quicker but it's actually worth all discussing about how the internals of this works because essentially what we have here is something called a task tree and what a test tree is is it it's how the system executes code or tasks that you run in parallel in terms of cancellation and you know resuming whenever they receive a result so let's go into a graphic and talk about how this actually works behind the scenes so you may not actually notice it but when you're actually working with tasks they actually do have a life cycle and by a life cycle what i mean is that when they're created and when they're cancelled and all that other stuff so what we're going to do now we're just going to look at an example and discuss how this works so let's look at our task tree which is essentially our parent task and all the child password in it and see how it handles a couple of scenarios so let's look at this example and let's say we have our task tree our parent task which is fetch content which is an asynchronous task so what this essentially is for us is just the parent task so within this parent task fetch content which is our main function we also have some child tasks in it and within this parent task for it to basically either return some kind of results or for it to throw some kind of error all its child has need to have finished so let's look at this so the first child task that we have is fetch users now this is a task that we have an asynchronous function that we have within our parent function so what we're saying here with this fetch users task is we've executed a request to fetch the users but something's gone wrong with the request and it's failed well what's happened now is because this task has failed what it's going to say is that it's actually finished executing and it's going to throw some kind of error but within our parent task we also have other child tasks so we actually have fetch post now what's gonna happen is swift is not actually going to automatically you know stop this task from just running because the previous child task is finished instead what it's going to do is it's going to cancel it so by canceling this task what does that actually mean so because we've cancelled this task what's going to happen is the system is going to wait for it to finish and then once it's finished doing its processing it will then just automatically mark it as cancelled so the result that we get back from this fetch post will actually be discarded and instead what the result we will use is the result from fetch users which was the error that was thrown now let's say if fetch pulse also had a task within it another child task which was executing on a background thread or something what would happen is that any descendants of that child task so a child of a child task would also have the same behavior as well it won't stop execution it will actually continue with its execution then it will get cancelled and then once it's cancelled it will then you know just discard its result so whenever a task is cancelled essentially what happens is it's this result gets discarded unless it's the task which failed so in our case the task that failed was our first child task that we executed which was fetch users now i know i've spoken a lot about you know cancellation and things finishing but what we're going to do now is we're actually going to look at how we can actually use task cancellation actually manage our tasks and cancel them within our child task if we actually need to so let's look into that now okay cool so if we actually go to our test cancellation and actually look at our sample and if we actually open up the task cancellation will not open up but you select the task cancellation scheme you'll notice that we have a user model and we also have a struct here to fetch content from the server so all this function is doing is it's just going to fetch and then the url that we want and the code the response and then we also have a function here to fetch users with their ids so what i'm actually going to do is i'm actually going to create a task within the function scope to fetch um the users with the following ids and we're going to try and fetch 100 users so i'm just going to type this out and then break it down for you just to show you another way of how you can actually create tasks okay cool so what we've got here is we've got a function called load and what this does is it matched with asynchronous because we want it to be um an asynchronous function where it's fetching something from the service we're saying here that the match users that we want to get is 100 the minimum users we want to get is one so what it's going to do is going to create an array of ids between 1 to 100 we're then going to create a task but this time if you notice we're actually capturing the value and for the task within this concept here called fetchtask and then what we're going to do is within our task we're going to execute the function to fetch users with the ids so the ibs here is the ids that we define here when we create our range of arrays of when we create our array of ids and then finally what we're going to do is we're going to just print out each user that we got back from the service now with our effects task we're going to use the results so we're going to say that we want to try and await this task i'm going to use the result to execute and kick this off so this result.get is what will actually kick off this task here so when would you actually want to use a task like this so i'm actually using a task like this because i want to fetch all the use and print them out into the console or in your case you may want to use a task like this with an actual scope so you can manage whether you want to cancel it or not is if you're doing something like um sending some data in the background or persisting something to a local storage or right into a file directory or something so that's when you want to actually use this example that i think are good use cases so what we're going to do now is actually create an instance of this stroke and execute our load task so let's do that now okay cool now let's run this you can see here it runs a few times and then it fails with an error so what's actually going on here so this api that we're actually using the uh json placeholder api it actually only returns 10 users per time as you can see because i executed 10 times so we're actually saying we want this actually execute 100 times now because of that we're actually telling the system like i said to you before when we actually get a failure or all the other tasks that we would have created will actually um now still execute but their results will be discarded so rather than actually even trying to attempt those um you know calls to try and get those users with ids that don't exist what about if once we reach the limit um we cancel it so we're going to do now is actually going to look at how we can use the cancellation with tasks to actually you know say that cancel this task so we're actually going to do is when we actually loop through all our ids before we try to actually get a user we're going to check here if the task was cancelled due to some kind of failure so i bought the um fetch call to get the user let's just write out some code and what this is actually going to do is when it actually loops through it's actually going to check for any cancellation so if a task was cancelled then what's going to happen is it's going to throw the cancellation error now what we're going to do now is we're actually going to check here if the limit has been reached so i'm going to create a another function here called limit function property called limit and after we um create our task what we're going to do is we're going to check to see if the ids so the ids is greater than the limit that we find at the top here then we're just going to cancel this task all together so we don't actually even attempt to execute it so let's see what happens now we run this as you can see here we actually get a cancellation error because we've already got too many ibs so let's not execute that and instead investigate why we have too many ids that we're trying to actually you know use in the first place and now if i actually reduce this from 100 to 9 and actually run this so because we have 9 we have nine which is less than the limit 10 and we can now actually execute and retrieve all our users and if i was to change this to 900 you'll see now that we don't even attempt to try to execute our task so we don't do any unnecessary um processing or there's also another way as well to check to see if a task is cancelled and rather than actually using this to try task to check cancellation we can actually use another way of handling this which is by using a property on task which is called is cancelled so let's see how we can use that as well so rather than using this if we were to just say so what we're saying here is that if the task is canceled and we just want to print the message cancel task and then break out of the um for loop and if we just run this now notice that we get the message saying here cancel task now you may be wondering what is the benefit of using this over the static function from before which gives us a cancellation error well if we actually get a cancellation error we could handle you know any kind of processing or any kind of code that we want from the cut statement well from the where the error is from but what this allows to do is it allows us to actually manually do any kind of custom you know code here so let's say for example if the task is cancelled we want to actually log to a logger on the system here's some information as to why that task was cancelled or if the test would cancel we may want to offer some kind of retry directly within the function in terms of where the task was actually cancelled so using this task is cancelled just gives a lot more control in terms of what we can do next within the actual function scope for where the cancellation is happening so let's just wrap up on tascam installation to summarize some of the things that we've learned so the first thing that we've learned the first thing you learned is that when working with async within you know swiftcon currency what will happen is that the swift framework will automatically handle task consolation photos and throw their own errors this is called implicit conservation also when you're working with your own tasks that you create i.e the function that we create where we create a task within the local scope you want to also handle cancellation yourself to make sure that you're not executing any code that you don't necessarily need to run and execute so this term is called explicit cancellation on top of that when creating a task you can actually use the cancel function as you saw to cancel a task to cancel a task and also as well you can actually check to see if a task has been cancelled and finally it's important to note as well that swift task actually is a concept called cooperative cancellation so now that we've actually summed up everything to do with them task cancellation the next thing i want to do is i want to look at the group task which is something that is a bit different to async uh let tasks so like i said before asynchronous tasks are really good if you need to actually chain multiple things one after the other and then at the end collect all the results but what about if you're in a situation where you're working with a task and you don't actually know how many times you need to chain an action so a good example is let's say you're using something like instagram instagram you don't know how many photos may come back on your feed but you may want to asynchronously run the task to download these images in parallel well this is where group casts come in and it can actually help us dynamically create tasks that actually run in parallel and run asynchronously so let's look at that next if you just go into our project here group tasks and make sure that the scheme group task is selected now the first thing i want to point out with swift ui when you're actually working with swift concurrency is there's actually a new modifier that actually allows you to execute um tasks specifically whenever the view appears and also as well these tasks will automatically be cancelled when your view goes off the screen so disappears and it's this modifier here called task now you may be tempted to use unappear and then put a task within unappear but personally i personally wouldn't do that and the reason why i say you shouldn't do that is because like i just said before the life cycle of this task for this swifty wide view is handled for you with this new task modifier and if you actually look within this task modifier as well this is where we create our photo service we actually get the results from our fetch photos function which we'll go over in a second and we assign those images to our state property here which is an array of pictures as well as dumping the results and any errors we have we can catch and handle them so what is this portal service so this portal service basically uses this struct here which is a photo and picture so within this photo service what we're doing is we're fetching a list of photos from the pixel photos api and then once we get our list of photos back from the servers we're then looping through each picture that we've received so we can actually download each image specifically so the reason why we're doing this is because we don't know let's see if we're building posts let's say we're building something like instagram we don't know from what image one two three four five is we need the api to tell us that and also as well when we're actually calling the service we don't want to call every single image that exists in the database because that would be quite heavy so instead of us doing that we first get the id or the url of the image that we want to download and then once we do that we'll then create a group task to asynchronously and also run code in parallel to download each image as well but we actually look at our implementation below what we're doing is we are doing this asynchronously where we asynchronously download each fetch photo that you get back from the service the only problem with this approach is that we're not actually running this in parallel and if we actually run this in parallel to make use of the threads that we have available with our devices we can actually improve and increase the speed of the result we get back from the service so just to show you this in action i'm just going to run this on the simulator as you can see when it finishes this is how many nanoseconds it takes for it to execute so what we're going to do is we're going to copy this and i'm just going to create a note of it up here so we can actually compare how quick the group task is compared to this so what we need to do now the first thing we need to do is rather than actually looping through each patch photo and awaiting the result for each one we actually want to create a group task which actually throws any errors and if something goes wrong and also creates a child task to actually execute and download this image in parallel so let's write this out now and i'll break it down okay cool so we've got our photos already here we're getting our list of photos but this time something is different so i've created a group task here using the web throw and task group now if you want to use a task group that doesn't throw any errors and you just want to execute a you know bunch of child tasks in parallel you can just remove the throwing and then you have with task group without any throwing capabilities but we want to use the throwing alternative now what we're saying here is we're actually awaiting the group task so all the child tasks to finish before we actually retrieve our final result and we're using the try keyword because it's possible for this to throw any errors now we've not handled the everest bit yet that's why you're seeing this yellow warning so within a test group you can actually return a value when a group has finished executing now in this case that we've got here i've actually marked this as void itself which means that this group will not return anything but let's say if you want to execute some code to actually log to a database or log to a file for or locally or local storage then you don't really need to return anything you can just execute your task within your child task and then fire and forget and keep it moving but what we've also got here is we've actually got our group and this group here is where we actually create our child tasks so within our task group that we created here we're actually looping through all the fetch photos that were received from the service with their ids and then we're actually creating a new task within this task group so this new task what it's going to do is it's going to fetch a photo thumbnail from the service put it in this um constant here called picture and then append the picture into the photos now if you actually look at it right now we actually do have an error and the reason why this is is because as of right now picture cannot be um mutated within this task group and the reason why that is because it doesn't actually conform to the sendable protocol and doesn't actually conform to the rules as well so although pictures are stroked we've not actually made it sendable and also as well we couldn't use a class because automatically classes also are not sendable so you need to handle synchronization the best uh solution for this would be to actually use an actor but we're not actually using actors just yet because we want to focus on task loops and break that down first but another example coming up we actually we will actually discuss actors so how do we solve this issue without using actors understandable protocol well what we need to do is we actually need to return a value within our group and then we want to asynchronously use that value to append to our array how do we do that well using void here we're actually going to use picture to actually return a picture when the task group has finished its you know asynchronous code so let's do that now and then finally robin was actually appending to the photos array we're actually going to return the picture that is given to us by the service so let's do that now as well and if you build it you should realize that the error goes away now what we need to do is that when our group child tasks all finish and they've all received all the images that you know we've downloaded from the service the next thing we want to do is actually append this to our photos array um asynchronously so let's do that now okay cool and also as well if you notice our error for our withdrawing test people has now gone away why is that so it's because we're using a try and wait here so we're actually saying here that we want you to loot through all the um items within the group that we retrieve from our child path and if something goes wrong with our try that we've marked there it will actually throw that arrow as to what's gone wrong now we append it to the photos already now now you may be wondering sunday but we're still technically looping through this group asynchronously yes we are but the difference here is that we're not actually we're not actually waiting for um something to come back from a service so this loop that we have here isn't going to take a lot of time because we already have our values from our child tasks so that's why we're able to do this afterwards and append to our photos array so what will happen now is once it's finishing appending to our photos array we'll then finally return those photos which will be used within our state variable within the task on our swift ui view so let's actually run this now and see what happens in terms of comparison with speed so let's do that now and if you actually go into the console and look at the time it took you'll see here that it's actually noticeably be quicker so our old value was this and now because we're using task groups to run these in parallel dynamically you can now see that the time is actually decreased so when you're using uh task groups and test group tasks i should say all task groups you want to use them whenever you have something that is dynamic so if you have a dynamic collection of items that you need to handle and in parallel that are heavy tasks you're better off using test groups now if you know that you have a fixed number of tasks that you need to execute i.e if someone logs in you need to retrieve their authentication code you need to lock them in and you need to verify their location so those are the three tasks you need to perform then it makes sense we just use async so that's the differences in when you want to use either one or the other because they both kind of achieve the same thing but they still have their different purposes now the next one we're going to talk about is unstructured tasks so with unstructured tasks we'll talk about how we can use those what they are and what the benefits are so let's do that now so i've shown you quite a few examples of how you can use tasks and we've looked at an example before how we can actually use tasks within swift ui but swift ui came with a task modifier and it allowed us to handle the lifecycle of a task for force automatically when a view appears or disappears what about we're in a context i.e a ui kit where we actually need to execute a task within viewed load well there's actually a term for this and it's called unstructured concurrency so let's break this down so unstructured concurrency allows us to execute our asynchronous code from or our task from a non-async context so a good example would be view and load now beauty load is just a you know function that comes with a view controller and executes whenever the view is loaded into memory but what we want to execute a task there to asynchronously download and fetch an image from a service well this is where we can use unstructured tasks also as well it's also worth knowing that in some cases you may actually need to run your task from outside of the state of where you created it or the scope i should say so what i mean by this so let's say for example if you have a cell or a table view with a bunch of images and you only want to download images once a cell comes into the view well it wouldn't make sense for you to download those images um in the uh background when the user may never even reach that cell so what we can actually do is we can actually create a task the globally and within a global scope within the view controller and whenever a table v cell comes into the view that is when we would actually say we want to start downloading this image and start this task so what are the benefits of us actually using unstructured tasks well if you the first one is it allows us to actually manage our tasks and also as well we can manage the execution and cancellation so with structured concurrency and a lot of it is handled for you in terms of task cancellation with unstructured concurrency you actually get a lot more control in terms of how you can handle tasks and how they execute and how they cancel as well as that we can actually offer and actually have more customization because we have more control over how tasks are actually handled and executed within the context that we start them in we can actually combine and mix them and create some really interesting flows with not a lot of code as well so what i want to do now is actually want to go into an example in ui kit this time rather than swift ui and we're actually going to look at how we can actually use unstructured tasks with the example that i said before with downloading images and see how we can use this within our code again i'll give you a warning that the code i'm using here isn't 100 tried and tested for production it's more just to show you the concepts that i'm trying to teach you in these videos so let's jump straight into this okay cool so if you just go into the unstructured concurrency xcode project and go into the view controller and make sure your schema is selected for unstructured concurrency you'll notice that you have a view controller here which has a photo service similar to what we've been using before we have a view controller which has a table view in it and we say the number of rows is dependent on the content that we get back from the service and you'll notice that we have some delegate functions here so we actually have um will display so whenever the cell comes onto the screen and did end displaying so whenever the cell and goes off the screen so what we're going to do is we're actually going to handle our tasks within these two delegate functions and see how we can use unstructured concurrency to execute tasks whenever a cell appears on the screen and also disappears off the screen as well so like i said before when i was explaining and breaking down on structured concurrency because we want to manage our task from outside of the scope of this function we actually need to create a global variable for holding and managing our tasks so what we're going to do is we're going to create a task property within our view controller and we're going to use the index path as a key and also the and also the task that we're going to execute whatever that cell comes into the screen so essentially a dictionary so let's type this out now so we've got our task here and you may be if you never saw this before what i've got is just a type alias the reason why i've used the type alias is just so it's clear in terms of what its purpose is so it's the bottle task and you can see the key that we're going to use is the index pathway in our dictionary and that index packet key is going to hold a value of a task which is going to help us download the image so before we actually just jump straight into the code as well in terms of actually writing it for downloading a um task i just want to point out something so when you're actually downloading um images in this case of what we're doing now you obviously don't really want to block the main thread so you don't want to actually run that code on the main thread because that's the main you know instance of when people actually you know interacting with your app and if it's a down there's a download that's big and you're running it on the main thread then you can actually freeze your ui so it's actually worth noting as well that when you're actually working with view controllers specifically in ui kit a lot of these delicate functions are actually automatically on the main actor now we'll discuss what the main actor is a bit more later but essentially what you need to just know for it for now is it's essentially the same as you running dispatch queue dot main async where you run code onto the main thread so a lot of these delegate functions are actually going to uh respond when we get when we actually get a result onto the main actor so we'll update our ui but our tasks will be run on a thread which is not on the main thread so let's create our first bit of code to actually create our task whenever the cell comes into the view so what we're saying here is from our view model we want to get our content at the index path.row which is the url that we want to load and then what we're saying is that we want to get the url from here and we're going to create a task with the index path that you're currently displayed on the screen and then within this task we actually want to run some code to actually download that image so let's write out now okay cool so now what we're doing is within our task we're going to use a do catch to actually catch any errors if an error was from or handle it from there so if everything was okay though within our do clause we're going to get the table view cell aurora index paths and try to cast it as a photo table view cell within this table view cell i actually already have an image view so we're going to set the image on our image view using the data from the service where we download our image and assign it to ourselves image view so what we actually have now is we actually have a task which is created based by its index path and then within that task we're then saying that when this cell comes off the screen you want to execute the code to try to download the image and assign it to our cell now it's worth knowing because we're handling the life cycle of the task ourselves and there's not really any cancellation handle for us we actually need to handle clearing the index path from the dictionary if it has finished so after they do catch what we're going to do is we're just going to use the same index path within our dictionary to clear any tasks that may already be there so let's do that now and you may be wondering why is this important to do well if you think about it right if we just kept on going and the scrolling up and down on the screen um we could actually have a case where we just get duplicate index password in our dictionary for duplicate tasks so by us running this what we're saying is we're basically clearing any unnecessary tasks that have a um finish and don't need to be there anymore just to keep things clean and not store this up so this is the one of the things with unstructured concurrency in terms of the way that you have to handle a lot of this stuff yourself if you are running code from a non-concurrent context but it's actually also one more situation that we need to handle so let's say for example if you're actually scrolling through the app now and you're trying to download should actually cancel it because the user is not looking at that cell so it doesn't need that data anymore to say so we can actually save resources by just canceling it so within our did end displaying let's actually write some code to actually cancel any tasks that may still be running when a cell goes off the screen cool so now what we've got is we've got this code here where we're basically saying if a cell goes off the screen we want to get that index path from the dictionary and cancel any tasks that may be pending and running so we've got everything that we need in place now what i'm going to do is actually run this and show you what this looks like in action so let's do that now and as you can see on the screen we're actually running this it's actually downloading our images now you may notice that like the images look random and that's not a bug the reason why they look random is that when you actually call the pixem api it actually generates a new image every single time but you can see here that our scrolling is quite smooth and it's not really blocking the main thread which looks pretty good but what about if in this case i've actually downloaded the image i now want to perform some extra code to maybe cache that image into a database or just cache it into a you know local cache and i may want to also log the result of what's happening onto the user's device well we can actually go one step further and actually run something called a detached task so let's actually discuss what detached task now is in the next set of slides so let's look into detached tasks so what are they so essentially what the task tests are is they're similar to unstructured tasks where you have to manually execute cancel and await their result but there's a small difference so the small difference between the tax task is that the lifetime of the task is actually not bound to the scope of where it's created so what does that actually mean so let's say for example if i actually created a detached task within another task that detached task is completely separate to any other task it's its own independent task it technically doesn't really have a parent task you could say so with a detached task if you actually run it with another task it actually technically can run concurrently with other tasks at the same time so you can actually run the tax tasks on their own um independently so they're not related to any of the tasks and also you can actually set priority on to them so where would this be useful so like i said before let's say if you actually need to perform some kind of action after an asynchronous code has finished then you can actually create your own detach task to perform something in the background that's when it would be useful now for me personally it wouldn't make sense to create a tax task with a high priority because for me it seems more like the tax tasks are more uh usable and you know make more sense to use when you want to do stuff in the background but there may be a case where you actually need to set the touch task with a high priority also on top of that if you want to actually create a detach task it's very similar to the task syntax except rather than using just task you actually task dot detach task and also on top of that as well because you're actually running a task with you know no pairing it can actually you know run concretely with other tasks so if you're actually using this detach task to modify some kind of immutable state so some other type of data you need to be very careful that you don't um you know create some kind of data race or access data from a thread where you know you shouldn't be um doing so what we're going to do now is you're actually going to go back to xcode i'm actually going to look at a use case in terms of how we can actually use the attach task to run some background tasks when a task has finished so let's do that now so what we've actually wanted to run a piece of code that actually simulates caching the image when it successfully downloads it so we just put it somewhere we'll pretend but in our case we just print something to the console and to show you of what what thread it's on so i want to create a detach task to actually cache an image so let's actually um go up to our service code and rather underneath our download function i'm just going to write a new function called cache so let's do that now okay cool so you'll notice that this function doesn't actually cache um you know to a local cache but if you want to actually cache it then you can put some code in here but what this is doing is it's just going to simulate caching an image and it's going to tell us what thread is currently trying to do on now you'll also notice as well that this function has not been marked as asynchronous and a reason why that is because we're not really needing to await a value we just want this to just try to cache the image in the local cache if possible and if it does cool if it doesn't then we'll keep it moving now the next thing that we're going to do is we're actually going to create our detached task after our image has been set and we'll set the priority to the background to actually cache it into the background so let's do that now cool so we've done a bit of refactoring and we've actually added unwrapping and actually retrieving our new image so we tried to get ourselves so you'll notice that and then finally what we're doing we created this new property here called image which is what we'll use to set the cells image view and then if you look here this is where we create our detached task so we've actually got attached tasks here where we set the priority of it to background so this will actually be performed on a background thread and it will actually cache this image so simulate caching it with the function we created above so let's actually run this now and see what happens in the console and see if we get um it's doing it on the main thread or not cool so as you can see here it's actually downloaded and those four images and if you actually look at a message here on the main thread is set to false though it's actually trying to cache well it's not actually doing it boys it would simulate trying to cache it on a background thread so we've got our detach tasks here but what about if we actually need to do um more than just caching it what about if we actually need to do some sort of over code in terms of like maybe logging it to a file um in terms of like the actions of what happened within a detached task what we can actually do is we can actually create a group task which actually spawns and does multiple things as well so what i'm going to do is just type this out first and then we'll break it down so let's do that now cool so let's just start from the top here so i've created a new function called log and this again this is going to simulate us logging to a file some information and related to the actions of what's going on maybe we might want to use this to help us debug an issue if someone's having an issue with downloading images on their device so if you actually scroll down you'll notice that within our detach task we now are using a task group and i'm not actually using wordpro and task because like i said before i want these actions to be fire and forget so this is just a task that doesn't actually handle any kind of throwing now within addy tax group what we're saying is that we want to create a task group which doesn't return anything within its um group and that we created and within the group that we create you'll notice that we actually have two ad tasks here so two child tasks that we're actually creating so the first task that we're creating um within our group is to actually cache the image and then the second task we're creating is to actually log the url locally onto the device now these two child tasks are actually part of this detached task and like i said to you before because the touch tasks are actually independent and they don't really have a parent these child tasks are actually part of the polls but like i said before the tax tasks are actually independent and don't have a parent but because we created a group task within the detached task what we're essentially saying here is that these child tasks can technically be cancelled if we were to cancel our detached task the life cycle of these child tasks it's all contained within this detached task so this is all isolated on its own which is why i was saying that you want to be very careful when you're using detached tests because if you're modifying a newer state within a detached test because it's isolated you may run into data races so you need to be careful so me personally when i'm working with detached tasks i'll probably only use it if i need to perform some kind of action after something's finished in parallel in the background that's when i see a good use case for it so if we actually run this now and see what happens you'll notice in our console that is actually simulating caching our images and also logging the url for our images to a local cache now if you actually had some legit code in here because i'm just printing to the console um it would actually perform the actual actions but you can see that it's serving its purpose so that's everything to do with tasks and we've actually broken that down the next thing i want to talk about is a completely different topic called continuations and we'll discuss what continuations are the purpose that they serve and their benefits so let's jump into that now so what are continuations so continuations basically allow us to actually bridge the gap between older code that may not support asynchronous functions also as well on top of that we can actually wrap our existing closure base code and actually use the new async capabilities so by this what i mean is let's say you have a function or you have a set of functions and a code base you may not have the time to actually update every single closure to use the new async await capabilities instead what you can actually do is keep your existing code that you have and actually just use the new async async await capabilities on top of this by using a continuation you can create a continuation you can create continuations that either a just yield some kind of result so a result would just be if it was an array of strings or an array of a struct or whatever and also as well you can use continuations to actually throw or yield an arrow so if you want to throw some kind of error where something goes wrong you can do that as well so what are the benefits of using continuations so like i said before it allows us to bridge the gap between async away and older code also as well not a lot of libraries have actually been updated to use and support async await now if you have a library that you're using that doesn't already support this or you have existing code in your code base you can actually use a continuation to basically hide that closure-based stuff and just kind of wrap it around the new async await stuff so it's also worth noting as well that with continuations they must reveal the result if you actually create a continuation and it doesn't return a result then the compiler will tell you that you know you're messed up so what we're going to do now is we're actually going to look at how we can actually add continuations to our code with another practical example so let's do that now so i've been working on an app that allows me to get anime quotes from some kind of api i've actually got this local swift package that been given to me that fetches these quotes for me so i don't really need to handle the logic in terms of decoding it myself so within this package as you can see it's actually got um a lot of code in there and it's still using the old url session way of retrieving a retrievement data from some kind of resource as you can see here so this swift package is actually part of the continuation escort project as a local package so it's not hosted anywhere so if i go inside of my project here so continuations and you'll see that the framework or local the package animate course is actually linked to this xcode project now if i go into the continuations folders content view you'll also notice as well that it doesn't actually have anything as of right now and i need to select the continuations yep you will actually notice it doesn't actually have anything as of right now so the first thing we need to do is we actually need to import our package into the switch to our file so let's do that now now because we've imported our package we get access to all the code that i just showed you a second ago now if i wanted to i could use that closure to just fetch the anime quotes and just display it on the screen but i want to use the new async await capabilities to actually handle this for me so i don't need to do this you know do use the closure syntax myself and the task will handle the um lifestyle management life cycle management for me for when the viewer appears and also when the view disappears as well so we're going to do is on our content view we're actually going to write an extension on it to write an async alternative with a continuation that does this for us so let's do that now okay cool so we've got our function here called get quotes and put your self-explanatory this gets quote is also going to be async and is going to return an array of pros and it's going to return an empty array so what we actually want to do now is we actually want to create a continuation so we can actually you know use our example from before in terms of um fetching the data with the service but this time we're going to wrap that within a continuation so let's do that now the first thing that we're going to do is we're going to create an instance of our service at the top here so let's do that now and then i'm also going to create a state variable so i can actually bind the result of quotes back to some kind of list that we'll use on the screen in a second okay cool so now we have our state property for holding our quotes which is going to be bound to our list which we'll use later and we have our service which is actually used to actually execute the um code to get our service what we're going to do is inside of our get post i'm just going to use that closure inside of here for now so let's just use it and now because we have our fetch quotes we now need to wrap this within a continuation so let's write this out and then break it down oh so let's just break this down so as you can see here we're now saying that we want to um return our check continuation but we're using the away keyword and we're also using with check continuation so this is how you create a continuation and you await it so when you get some kind of results you want to await it and return the result so within here we have our continuation that we're going to use to resume and this is going to return our quotes now you'll see here that we're actually forced on rapidness which is not the best practice and we're just getting the value and we'll improve this in a second but for now what this is going to do simply is just create a continuation that we return with an away an array of quotes since we're returning that with this bit here so what we want to do now is for now we actually want to write out some code to actually show a list of these quotes and actually attach a task to set our quotes as well so let's do that now so as you can see we've got our list and we're just using a simple for each to loop through our quotes we're using the anime property within our quote as the unique identity file and we just got a v stack which just displays some information on the screen about the anime and the quote as well so what we need to do now is attach a task to our navigation view so once this comes onto the screen it will actually execute i'll get quotes and assign it to quotes so let's do that now and as you can see here within our task we're just saying we want to set the quotes and await the value that we get back from our get quotes which is an asynchronous function so let's run this and see what happens and as you can see we get a list of the quotes from the anime service so this is working technically fine right but this falls on wrapping am personally not a massive fan of it so what i would actually prefer is that if something goes wrong i actually want this get quotes to actually throw some kind of error so let's look into fixing this now so rather than using a with check continuation there's actually an alternative as well you guessed it we've checked throwing continuation and with checked throwing continuation it allows you to yield some kind of results and also yield some kind of error so let's do that now cool so you notice that there's a few changes that we've made and we'll start from top to bottom the first change we made now is that this function is not just async anymore it's now asynchronous because it can throw an error also within our check continuation we're not just returning our continuation on its own anymore we're now using a try before the await because we also want to try to use this continuation because it could possibly again throw an arrow now within our fetch quotes because we're using the result type with swift we're checking if it's a success then we're just going to do what we did before and return our quotes or if there's a failure this time we're going to do is actually resume it but throw some kind of error so we actually handle all the possible paths here now what we want to do is we want to actually fix the error that we have here so what we need to do is actually mark this with try and also use a do catch so let's do that now okay so we've got to do catch now and similar to before the only difference this time is within our d caps we now use triad weight rather than just wait and within our cash we also print out any errors now if you wanted to at this point you could actually you know handle any error handling yourself so show an alert to say what's gone wrong go do whatever it is you want to do but this code in my opinion now is a lot more safer than you force on unwrapping if something was to go up wrong so let's just run it to make sure that it's all fine and it's all okay as you can see when it finishes and executes we actually get our list of anime similar to before with their quote so everything is looking fine and it's also handling the cases for where things could go wrong as well within our continuation so just looking at this you can see here how we had a local package that uses closures but we didn't have the capability of async await and you know you you may not have the time to or the library or whatever it is you're using the project that you're working on may not have the time to update it to the latest version of async await so this is where continuations are really handy because they allow you to bridge the gap between that old existing way of using closures and now the new way of having async await functions so the next thing we're going to look at is the big one that i'm guessing we've all been waiting for because this is the one i kind of lied to just the one that set my head west when i was trying to learn it it's actors so let's actually look at actors what they are how we can use them and also the benefits of using them within our code so let's do that now so we spoken a lot about tasks and you know async away continuations all that kind of stuff there's actually a new you know feature that we get with swift concurrency called actors so you may be wondering what actors are well well essentially what actors are is that they're a new type of object that we can use that allows us to protect mutable state you may be wondering what is mutable state so a mutable state is basically any object that can be modified after it's been created so just to give you an example of this so if we actually look at the example below i've got a class here which i've called user and what i'm doing is i'm initializing that class with my name so because i'm using a class here and it's a reference type whenever i actually change this property my name the value in memory also changes as well so it's updated in memory so you may be wondering what problem does this then solve with actors well if you think about what i just said this object that we have here class because we actually created the object and it's in memory and we can actually modify it you could introduce something called data races if you're actually modifying data across multiple threads because remember we spoke before about concurrency and how you can actually you know perform tasks across multiple threads in parallel to speed up performance well this can introduce data races so what problem do you have to solve they help us prevent ourselves from encountering data races when we're writing code that is concurrent and make sure that we protect their state so that if we do modify something across a different thread we don't introduce like bad access error crashes or you know invalid they are so we've spoken a lot about theories but not actually dive deeper into what a data race is so essentially a data race is when one or more thread tries to access the same object or data at the same time so you may have come across it before where you've been writing a program you get an error that's like xc thread access access or those type of crashes where you can't really understand what they mean and sometimes these are data races because you're trying to actually modify an object between two different threads it's also worth knowing as well that when you're actually modifying this data it's actually potential that you get inconsistent data or you could actually get a crash as well swift actually already has ways to prevent this issue so some examples is using atomics locks and also dispatch queues is allowed to do is they actually allow you to basically write synchronization code so you can ensure that when you want to access data between different threads you actually protect it safely but the thing is with this is that using these you know things that we already have already provided to us by swift it can actually already be a bit confusing and not very easy for you to understand if you're a first timer with these issues and this is where actors come in so actors actually help us bridge the gap between this so we can actually have a object that comes with synchronization automatically and allows us to actually protect our data across multiple threads i actually want to break down what an actual actor is because we've not spoken about that and basically one actor is is is basically a new object like i said before that allows us to protect the synchronization of data across both reds it's also worth knowing that with actors because they have their own synchronization if you want to actually update an object within an actor you actually have to use the actor itself so we want to change and update a counter value in an actor then we have to use the actor's function to do so and finally actors are actually very similar to classes in a way because they're both actually reference types the only difference between them is that with classes you don't actually get that automatic synchronization handled for you it's something that you have to do yourself and you can actually accomplish this if you wanted to by using the sendable protocol but with actors you don't need to worry about that implementation of synchronization it's handled for you so the next thing we're going to do is we're actually going to look at an example of how we can actually use actors within our code with a very simple counter example then what we'll do is we'll look at more examples of actor and basically see you know how we can use them in practical examples so let's get into that now all right cool so if you actually go into the xcode project basic active example you should see an empty view controller and again make sure that basic active example is selected on the schema here so what we're going to do we're just going to write out some code for a very basic count or to show you the issue that actors help us solve so i'm going to just go write out some code for a counter class and then we'll just break it down it's not going to be anything too crazy all right cool so we've got our account on and we've got a private set property where we get the um where we can get the value and we also get on another function here which returns the value and increments it as well so let's say in this case we discount all we actually wanted to you know increment it from two different threads so we actually want to concurrently or i should say in parallel increment vm counter so what we'll do now is actually write some code within the view to load to actually trigger this okay cool so what we've got here is we created that account off and we've got two detached tasks so these are two completely independent tasks which will increment the counter um for us now you may be looking at this and thinking that what is the actual problem that this guy is trying to solve right well if you actually run this enough times because our class doesn't actually handle impromptu because our class doesn't actually handle synchronization it's actually possible for a this this to crash and also as well for us to actually get inconsistent data so what do i mean by the inconsistent data part so because we're actually not handling synchronization ourselves it's actually possible for our counter to be 1 1 or 2 2 depending on how quickly this actually actually executes now i'm going to try to run this and see if i can actually reproduce it um because it might you know it's not guaranteed but let's just see what happens okay cool so i'll just run that now on my second attempt i've now got it here where it says 2-2 which is actually not correct so technically with this counter the only possible values you should have is one two and two and one you should never have two and two because um we want it to increment twice um so it shouldn't be two two on both of them so how do we actually solve this problem well in order to actually prevent this two two from happening we have to actually just use actors now in order to use an actor it's actually very very simple it's nothing too crazy all you need to do is change the um declaration here from class to actor like so and now our counter is an actor and as you can see we've got um the instance of it similar to how you basically create an object like a class or a struct or anything like that so you'll see now that we actually have an error here saying that the expression is async so remember i said before about how actors handle their own synchronization because of this you actually need to await whenever you want to perform an action so in order to use this function from our actor we actually to use the await keyword so that the system handles when to suspend the function and how to handle synchronization for us so let's add a weight in front of these and you should see that the errors have now gone away so what we're going to do now we're going to just run this a couple of times and see if it's possible for us to get um one one or two two again so the only values that we should get is one two or two and one so i've actually run that five times now and i've not actually come across it where it's giving me one one or two two i've only ever got one two or two and one which is what we want because we only want our account to be incremented by one now although actors do help us solve this problem in terms of making sure that our objects uh you know synchronize and we don't get any invalid data and we don't get any we don't get any you know possible um crashes from writing between multiple threads there's actually still something that can go wrong with actors and you can actually still introduce um possible data races you can still introduce possible data races where you actually get inconsistent they are now apple when they actually look at that when you actually watch their wwe see video actually talk about something called actor reentrancy so we're going to do now is we're actually going to go to the slides now and discuss at the reentrancy and when this would be useful and what it actually is so actors are actually a great tool to actually help us with handling synchronization of objects between multiple threads when we're actually writing concurrent code that is needs to be executed in parallel although they have their benefits of when we want to use them we still need to actually prevent ourselves from introducing something called actor reentrancy so although we actually access this data across multiple threads it's actually still possible for us to actually produce inconsistent data within our actors so you may be wondering like how does this happen well remember what i said to you before a second ago because we're handling synchronization and we have and now our actors actually run their code asynchronously so what could possibly happen is that if you're actually running are using a function i.e like our counter example from before if we actually modified the state of that data whilst the system suspends that action it's actually possible that when it now resumes we actually have inconsistent data so this is what active reentrancy is it's basically ensuring that when you're actually using actors and you're modifying mobile mutable state you basically make sure that you're checking to see if your state is still valid and if it's still consistent what we're going to do now is we're actually going to look at a simple um bank exam bank account example to show you how you can actually still introduce a database within your applications using actors and how you can actually solve this issue by ensuring write checks in the right places when working with actors so we said that we'll go write a simple bank account example so what i'm going to do is i'm just going to start typing out some code and explain what we're doing and you know what its purpose is so at the top here i'm just going to write a basic actor called bank account for now just break down what it's trying to do okay cool so we have a bank account and it has some errors in terms of what could go wrong and we also have a balance that we're trying to manage within the bank account so right now we have like 100 pounds you may also notice as well that for the balance i'm not using a double or a float the reason why i'm using decimal is because it's actually more precise and it's actually recommended when you're working with currency to use decimal over stock like um floats so you can actually get more precision when rounding up so the next thing you're going to do is you're actually going to write a function that allows you to withdraw money from your account so let's do that now okay cool so we got our function here called withdraw which takes in an amount which will be a decibel and essentially what this tag is is just so we can identify which task is performing the withdrawal the next thing we want to do is we want to check to see if the user actually has sufficient funds to actually withdraw money from their account so let's do that okay cool and all this functions doing is it's just checking if we have sufficient funds um and with the amount that we want to withdraw and it just tells us whether we can withdraw that amount or not what we want to do within our draw function now is actually check to see if the user has sufficient funds and if they do continue if they don't then we want to throw some kind of error so let's do that now so cool as you can see i just wrote a print statement at the top here so we can actually tag which um task is executing this and just prints a message in the console and then finally we just check to see if the user has sufficient funds then we can carry on if not then we'll throw an error to say that there's been sufficient funds and we also print a message so we can actually track what's going on in the console so when you're actually you know withdrawing money from your account we actually need to call some kind of service now i'm actually not going to write out the exact api code to do this because i don't have an api to interact with for now what we're going to do is we're actually going to simulate authorizing an account and we're just going to put our task to sleep so we can actually simulate it calling an api service so let's do that now so as you can see we're authorizing the transaction and we're just simulating and us putting the um system putting the tasks uh we're just simulating putting a task to sleep so this will actually pause when you call this puncture function actually pause your task for a second it's using nanoseconds here but it's essentially one second and by default we'll just go always return true because right now um i don't really have an api to actually do it properly so we're just going to try to we're just going to fake it now the next thing we want to do now is after we have sufficient funds we actually now want to authorize if the account can actually make that transaction so let's do that now all right cool and as you can see we've got a message saying that the account has sufficient funds to withdraw attempting to authorize the account with the bank if it does allow us to authorize the account then we'll continue from the guard if not then we'll throw an error to say that the account failed to authorize finally if you've actually passed authorization we'll actually go ahead and deduct the money from your account and we'll just print some messages to say that if it was successful or not let's do that now so as you can see we're just putting out that the account pass authorization we subtract subtract the amount from the balance and then we just basically print out some messages to explain what the new balance is and how much you actually attempted to withdraw so the next thing to do now is actually use this actor within our actual view controller so let's go down to this view controller i'm just going to write some code to actually use this so let's do that now okay cool and as you can see i've just created the accounts or bank account and i just created two tasks just to show you two independent tasks that i've got to be running within the view did load and what they're going to do in the first task is in the first task we're going to try and withdraw stem and then in the second test we're going to try and we'll draw eight so let's actually just break down what should happen so we've got 100 in our account right so we actually tried to withdraw 70 so what should happen is that when we get to this 80 it should actually give us an error to say hey you can't withdraw any more money so we shouldn't hit a negative we should only be left with further in our account so let's actually um run this and see what happens we just run this and let's just look at what we get here so on our first task we attempt to withdraw 70 it and the account has been has some it has the funds of server air it tries to authorize it and then when it's trying to authorize it remember this authorized transaction actually puts the um task to sleep to simulate an api request because of this the system suspends at this point so when the system suspends it at this point it actually now begins to start the second task and you can see this tries to withdraw 80 and then 80 again and then the second task that now suspends as well so because the second task is now suspended the system decides to resume the first task after a second and now what it does is it now realizes that oh it passed authorization we try to withdraw 70 and we have a new balance of 30. but this is where our problem is if you can see the problem now in our second task when it actually resumes and enters back into the system it's still continuing to say that oh yeah you passed authorization also as well you would drew 80 and after that you want to take off 50. so where is the problem in our code so what we're going to do is we're going to actually go back to the slide and actually look at the steps that are happening again and see if we can identify what the problem is and how we can actually resolve it so let's do that now like i said before this is the steps that we're doing so we want to check to see if you can withdraw funds we want to call an asynchronous function to authenticate your account the system suspends the free of resources to allow other you know tasks or you know call to run and execute the async returns the since the system is resumed and then we mutate our balance so look at these steps where is the actual issue well the actual issue is literally right here so why is that a problem because like i said before because we're actually telling the systems to suspend this task we're actually not protecting our state and verifying our immutable state after the system resumes so what is a fix for this well what we should do is apple actually advises that you should actually only alter your actors in synchronous code so what does that mean so any code like i said before the very start of this video that doesn't require you to wait for some kind of response that is where you should modify the state of your actor so if you need to interact with the api service and then you're then performing mutations after that probably not a good idea like i said before we currently have a suspension point between our read where we actually check the balance on our right where we actually modify the balance as well so in order to fix this all we need to do is that after we return and our system resumes back from our suspension point we just need to then check and verify that the state is actually still valid in this extra check will actually allow us to re-validate our state actually ensure that when modified when we're modifying the state of our actor it's actually going to produce consistent data let's go back to our example and actually look at how we can apply this fix to our bank account so the fix for this is actually quite simple so all we need to do is after our system resumes and continues after the authorization we actually want to add another check before we modify our balance to see if the account still has insufficient funds so what we're going to do is we're going to copy this and then all we're going to do is just paste it after our account has passed validation authorization i should say okay cool so now what we're going to do is we're going to just run this and look at the logs and see what happens cool so let's just walk through this you can see here our first task as our second task actually executed first and what happens here is it attempts to withdraw 80. then because we have sufficient funds in our account because we have 100 it passes that and then it tries to all friends authorize it with the bank so this is our suspension port in terms of it trying to interact with an api service then that's the first task attempts to withdraw 70 again a check to see if we have sufficient funds it does and then at this point this is the second suspension point for our first task now what happens is that our first task has actually passed authorization our first task actually returns first and the system is decided the first one is what we want to actually execute in this case it's actually going to withdraw 70 successfully and tell us that hey you now have a new balance of 30 and also as well because our second attacker is now resumed back from and the suspension point you can see here that we now get error telling us that we have insufficient funds and the reason why that is because once our second task resumed we put another check in after our suspension point to make sure that the data is actually the state is actually valid so we actually make sure that you can actually just definitely withdraw the amount so if you actually go to the actor isolation xcode project and just go we'll just start off in this final shopping basket i know this has got enough to do with shopping right but basically what happened was i changed my um mind at the last second so you can tell us the last minute change so i'm in this bar which is actually related to students and teachers what i want to do is i want to discuss how we can actually you know use actors and how they actually can still use the protocols that you can use with like classes and strokes so what we're going to do is we're just going to walk through the different types of protocols that you would use with these types of objects and see how they apply to active as well so with this um feature that we have here with this teacher that we actually have here you can see that it's an actor that actually has an array of students and it also has an id but what about if i actually want to do some kind of comparison between two actors well it's actually very easy for actors to come conform to different protocols such as equatable so let's see how we could do that now so now that we've done this you can see here that it's actually very easy for you to make a teacher equatable by just using the equatable protocol and implementing the function that it needs and in our case to do the comparison we're just going to use the identifier that we have for the teacher so now what i want to do is actually want us to actually use this um equatable within our viewed load so let's do that now let's just run this and see what happens you can see it tells us that these teachers are not the same so it's false because obviously teach one and teacher two are not the same but if you just change this to teacher one and just run this you can see that they are the same because teacher one and obviously teach one of the same so what about we actually needed our actors to actually conform to hashable well again very simple we just use the exact same um implementation that we would with any other object type so let's do that now okay cool so this is how you would basically conform to hashable like you would with any other you know class or struts or you may realize that like hmm we've got an error on the flashable but we don't have an error on the equatable what's different well if you actually look at our equatable we're actually using a static function so when you're actually working with actors and you're conforming to these protocols if it's a static function the reason why you can easily get away with it like this is because with static functions you don't actually need to create an instance of the teacher and you can just use it on the actual type itself so you can actually modify the state of the actor from outside with a static function but we actually look at our hash into function it actually isn't static so it's actually possible for you to actually modify the state of our actor from outside if you wanted to now in our case because we're not actually using this hasher to actually you know mutate any kind of data within our actor we actually don't want this to even be to even have the um you know the same synchronization capabilities that you would get with an actor so how do we resolve this well there's actually a keyword you can use with um actors called non-isolated which actually allows you to opt functions that you don't want to be part of the actors synchronization out so let's do that now so as you can see now we our error goes away so we have the non-isolated keyword on both our hash value and our hash into so some cases where you want to use non-isolated within active is if you're in a situation where a function that you've written a function that you wrote or if you're using something like hashable doesn't actually modify the states of that actor so if you need to opt out use non-isolated so what we're going to do now is we're actually going to use uh the capabilities that we get with hashable to actually create a dictionary of teachers and the subjects that they teach so let's do that now okay cool so as you can see in our view controller we've got our teachers which is keyed by using the teacher objects because we're able to use this because of the hashable protocol and they have their own array of subjects so just to show you that this is actually set and working properly we're actually going to loop through the teachers and their subjects and actually print them into the console with um commas separated so let's do that now so as you can see here i'm just basically getting the dictionary and accessing it via teacher one and this compact map just basically combining and getting an array of all the um subjects via their title and then using the comma to separate them so let's actually run this and see what happens as you can see we have a message here that says teacher one teaches maths in english so that's everything now so you may be wondering um and you know we spoken a lot about actors and how they handle synchronization or you may wondering how does that even work so before i set a keyword called sendable and what actors do under the thought that you don't actually see is they use this new protocol type which actually allows us to handle synchronization for us now what we're going to do is we're going to explore the sendable protocol and actually see how we can use this ourselves if we want to actually perform our own custom sendable objects and the benefits that they play um when we're working with code that executes in parallel so let's do that now like i said before actors actually allow us to modify state across multiple threads because of the sendable protocol by default like i said before so our act actors automatically opt into this protocol so you don't need to actually use this on an actor it's all it's also worth noting as well that different types can actually be you know sendable so and obviously an actor is sendable because it automatically opts into it a struct can also be sendable as well because it's a value type so by that what do i mean so because when you create an object of a strut and if you were to modify it you get a copy of it so you don't actually if you were to change it on a more on a different thread you're not actually changing the same instance of it you're just creating a brand new copy with a new set of values but with classes they are not automatically sendable and the reason why that is because like i just said a second goal with strokes classes are not value types they're reference types so like i said before when you put an instance of a class you actually have it listening in you have to have it and you actually have it sitting in memory so if you actually change that object in memory it actually updates it and you don't get a copy of it it just changes and updates it with classes you actually have to handle synchronization yourself like i mentioned before if you usually use locks atomics or stereo dispatch so you may have heard me talking about main actor so you know you may be wondering what is the main actor well essentially the main actor is a new annotation that we can use that allows us to actually execute our code onto the main thread so when you're actually working with applications you want to make sure that whenever you're doing updates towards the ui or executing important tasks which need to be done immediately you want to be using the main thread to run these tasks obviously before when you want to do this you'd actually use dispatch queue dot main not async and this would be a closure that you would use across your code to essentially execute these tasks on the main thread now all you need to use is just the app main actor annotation and you may have showed this in one of my previous videos where we actually talked about mbvm and swift ui with async way and i've also discussed why it is you want to use the main actor it's also worth knowing as well that by default when you're actually working within ui kit with stuff like view controllers these automatically are opted into the main actor so you don't actually need to worry about annotating this on the main actor as well some cases as well you may not actually want some functions of yours to actually execute on the main thread i.e if you're running some caching code for example this may not be something that you actually want to be on the main thread in order to resolve this like i said before with actors you can actually use the non-isolated keyword and what it will actually do is it'll actually allow you to opt out of specific functions and properties which you don't want to be accessed via the main actor so that's everything in this video what i'd actually like to do now is just give a few people a couple of shout outs who i feel helped me understand with concurrency a lot and also as well i'll direct you to some resources that i found very very valuable in the description box below so you can actually check out their blogs and their articles so i just want to give a shout out to these people so one of these people is apple but i want to give a shout out to wwdc for their videos on switch currency i'd highly recommend you check that out and you know watch and listen to what it is about swifton currency with their examples as well also as well i'd give a shout out to paul hudson who has great content as usual you know paul doesn't miss and i'd also want to give a shout out to donnie wall as well who has a great blog on swift concurrency and also some of the advanced concepts that he talks about are really good i'd like to also give a shout out to andy abbas who also has another great blog which breaks down everything in a nice clear way about switch concurrency and then finally i'd like to give a shout out to switch senpai who also does another great blog who actually gives and great examples in terms of how to use actors and all that other good stuff definitely check out these guys resources on these topics and also keep on learning as well and like i said to you before this video is a big video so feel free to pause go back restart and watch it and do whatever you need to do for you to learn with concurrency so if you enjoy this video i'd love to hear your feedback in the comment section below also as well if you haven't already if you give the video a thumbs up i really appreciate it as well and finally if you haven't already hit the subscription button to subscribe to the channel and the notification bell to get updates whenever i release a new video that's everything from me i'll catch you all in a bit deuces you
Info
Channel: tundsdev
Views: 2,163
Rating: undefined out of 5
Keywords: swiftui, Swift concurrency, Swift async await, Swift actors, Swiftui actors, Swiftui async await, Swift 5.5, Xcode 13, Getting started with swift concurrency, Swift main actor, Swiftui main actor, Swiftui tutorial, swiftui swift concurrency, Swiftui async task, tundsdev, Swiftui 3.0, Swift concurrency practical examples, swiftui mvvm project structure, swift concurrency continuations, swift concurrency group tasks, swift concurrency group task
Id: U6lQustiTGE
Channel Id: undefined
Length: 113min 10sec (6790 seconds)
Published: Sun Nov 14 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.