How to use Actors and non-isolated in Swift | Swift Concurrency #9

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] so my hope is if you're watching this video you also watch the last video on my channel because in the last video we dived into structs versus classes versus actors and the whole reason we did that was to get a deep understanding of what actors are and so if you don't know at the beginning of this video i mean you're welcome to continue watching but i highly recommend watching the last video first and then coming back to this one in this video we are going to dive deeper into what is the problem that actors solve because actors are relatively new to swift and they were added for a reason they're solving a very specific problem and i'm not going to tell you what that is because if you don't know that means you didn't watch the last video what we're going to do in this video we're first going to address the problem we're going to recreate the problem without an actor then we're going to solve the problem without an actor this is what we would have had to do if we didn't have swift concurrency and then finally we're going to introduce an actor we're going to see just how awesome powerful easy actors make our lives all right welcome back everyone i am back in our xcode project i'm about halfway through this course now i'm feeling good i'm feeling back in the game here it's been a long time since i was making youtube videos those first couple videos were pretty tough especially the last one we just did we covered structs classes and actors that was a very heavy difficult video to make and that gets us over the hump the rest these videos should be much easier than that in this video we're covering actors we briefly learned what actors were in the last video if you were not with me in the last one watch that one before this one the last video really explained what actors are in this video we're just going to dive in more on how to actually use an actor rather than what an actor is so let's right click the navigator create a new file it's going to be a swift ui view and let's call this one actors boot camp go ahead and click create once we are inside uh let's get coding here and what we're gonna do in this video there's three parts to this video firstly we are going to look at what is the problem that actors are solving secondly we're going to look at how was this problem solved prior to actors because actors are relatively new so there obviously were ways to solve this problem before actors and then finally we're going to do an example that really outlines just how easily actors can solve the problem all right so what is the problem that actors are solving and the problem really is a data race problem it is when two different threads are accessing the same object in memory so what we're going to do is first actually recreate the problem all right i'm gonna try to move fast here because there's a lot of setup that we have to do in this video uh basically we're gonna create a tab view i'm gonna create two tabs in our in our app let's just put it as a text it says hi for now let's give this a tab item and we'll use a label let's use the system image completion here with system image first screen let's just call it home screen house dot phil will be our icon let's copy that and make a second one and this one is going to be browse let's give this a magnifying glass cool we got our tabs down here um let's just make sure the tab bar is working i can click this awesome let's create two views for this so um up here let's create a struct let's call this one home view conform to view every view needs a body let's make it a z stack let's give it a maybe a background color of color dot uh let's do gray maybe opacity 0.3 and let's ignore the safe area let's put the home view on the screen as our first tab see what that looks like [Music] let's give it maybe 0.8 make it a little darker uh and then i'm going to create a second screen and call this one the browse view let's do maybe color dot yellow same deal we'll put this one down here as our browse view let's make sure we can switch between these tabs here cool and these are gonna be super simple views just to outline the problem here so we're gonna do is create an at state private var text of type string set equal to a blank string and then we'll create a timer we'll say let timer we'll set it equal to a timer dot publish we'll publish every 0.1 seconds we don't need a tolerance we're going to run it on the main loop we're going to run it in the common loop and options will be nil let's auto connect so that it starts as soon as this view is created on the screen we're gonna put a text that obviously has our text let's give it a let's give it a font of maybe headline and on this z stack we're going to call that on receive so every time we receive a value from a publisher this timer is a publisher it's publishing a value every 0.1 seconds so let's receive values from that publisher and then every 0.1 seconds we're going to get this new value here we're not going to use the new value so i'll just put it as an underscore but every 0.1 seconds what we want to do is call a function in a shared class and that class is going to be shared between this view and this view so up here i'm going to create a class and i'm going to call this one my data manager open brackets so let's make this one a singleton so we'll add a static let instance and we'll set it equal to my data manager so this will be a single instance that we use for this class across our whole app because it's initialized inside this class we can just add a private emit like this so that this is actually the only place we ever initialized one i have a whole video on my van series why i do not recommend using singletons like this but for this example it is by far the easiest way to go about it all right so in my home view i'm going to get a reference to this instance so we'll say let manager we'll set it equal to mydatamanager.instance what we're going to do is simulate some data that we would have in our actual app right so our data manager probably manages some data so we'll create a variable and call it data and it'll be just an array of string for now and we'll set it equal to a blank array let's create a function in here called maybe get random data and this function will return us an optional string what we're going to do every time we call get random data we're going to do two things first we're going to add some random data to this array so we'll say um self.data.append and we'll just append a uuid dot string this is just generating a random string and we're going to append it to this data array and then we're going to return data dot random element so a random element from this data array while we are in this function let's also just print out the thread.current the current thread that we are running on all right so on my view here every time we get it a publish every 0.1 seconds let's try to get some random data and if we get random data let's update our text so we'll say so in here we'll say if let data equals manager dot get random data so if we get random data we'll set self dot text equal to data all right before we move forward let's see if this is actually working so i'm going to click resume on the canvas here it does look like we're getting our changing user id uids here i'm going to build this to a simulator so let's take our actors and boot camp view make it the first view in our app let's build this to a simulator i'm going to iphone 13 here the reason i'm doing that is because i want to get that print out where we are printing the thread.current all right so our view loaded up here every 0.1 seconds we are getting a publish and we can see that every single print out here says it is on the main thread and that's thread number one and so uh that's kind of what you probably expected we're running this all on the main thread we never did any background threading here and everything's working fine there's no problems in our app right now what we're going to do is basically mimic what we're doing here but also do it on the browse view so i'm going to take my manager my text my timer copy and paste them down here let's add the text to our browse view and let's add this on receive to our browse view as well let's change this one to actually every 0.01 seconds so these will go at different cadences but eventually they will be occurring at the exact same time because they will publish at the exact same time and let's build our app one more time we are going to load it up i mean go to my browse view it looks like it's working i go to my home view it looks like it's working all of my printouts are again still on thread number one and that's why i don't have any problems yet but in our actual apps a lot of times we are using background threads and we don't have any in this example because we're not actually using any kind of url session we're not going to any server and actually downloading anything but we could and if we did do that we would go on to a background thread to do that downloading process so to simulate that what we're going to do is before we call get random data let's go on to a background thread so we'll say dispatch q dot let's use a global thread i'll make this one maybe in the let's do the background we'll call async and then we're going to do this action in the background thread and we know as good developers that if you're in a background thread and you come back and you want to update your ui you have to go back to the main thread so we'll add a dispatch q main async here and we'll go back on to the main thread i'm going to copy this and do it on our browse view as well except this one let's do a different background thread let's do a maybe do maybe let's do the default background thread why not so let's build this to the simulator we're gonna get a bunch of printouts here uh let's see i'll pause this so just looking really quickly we can see a whole bunch of threads that came through thread six we have thread nine um thread four later we went down to thread 10 12 13 17 19 30 41 tons of threads opened up in our app here and the important thing that we know as developers is that these threads are all accessing the same class and we learned in the last video that uh classes are not thread safe so if two threads access the same object in memory at the same time you can run into really bad problems in your app you can run into data races if not worse crashes now as we ran this we didn't get any error message the app was still running and this is probably a very common situation in apps especially beginner developer apps a lot of times you don't even know that your app has potential threading issues in it especially if you're not aware of these problems luckily xcode actually has a hidden thread sanitizer that we can use so if i come up here to click edit scheme the scheme that we are currently in and i turn on the thread sanitizer it's turned off by default if i turn on the thread sanitizer close this and build this one more time the third sanitizer basically checks the thread safety and as soon as we run it i can see some funkiness printing out here i'm going to pause the app so we have the same thing that happened except we got this massive we got this massive warning that printed out down here warning swift access race we are modifying a variable at this pointer remember in the last video we talked about references and pointers uh so you'll notice that this pointer same as this pointer but you'll notice here it's getting accessed by thread five here it's accessed by thread one the bottom line here is that two different threads are trying to access the same piece of memory in the heap and that is a problem that is a data race i guess it comes down here thread one and five i'm not really sure which threads are causing the problem but we know what the problem is multiple threads are accessing the same class all right that's the problem we do not want this in our app ever if you do have this in your app this is probably one of the hardest things to debug but now that we understand the problem we can start to address the solution so firstly the question is without actors how do we solve this problem we have two different places in our code and in your app you might even have more than two places that are accessing the same class from different threads well the first step is to take this class and try to make it thread safe so how do you make a class thread safe well basically you make everything in this class run kind of on the same thread or on the same cue so how we did that before we had actors was we would create some sort of cue specifically for this class so i'll do like a private let we'll call it q and i'll set it equal to a dispatch q now you'll notice dispatch q same word we that we use down here so when we go on to the main thread we dispatch onto the main we go into a background thread we would dispatch onto the global and then here we can actually just create custom cues so i'm going to create one with a label and i'm going to call it uh similar to like a bundle id i'm going to call it com dot my app name dot and i'll call it my data manager so that'll be just the name of the queue that will come up in the debug logs and the question then becomes well how do we use this queue well when we go to do anything in our actual app let's go on to the queue first so before we do this we'll call q dot async and then we will do all this code inside our queue looking good so far the problem we then run into of course is that we can't return out of here because this is now an asynchronous block and as we discovered earlier in the series well the old way of getting out of asynchronous blocks is with completion handler so we would have to then add a completion handler let's make it a function that returns void and the completion handler i'm going to give it an underscore and let's call this maybe returned title and it'll be of type string all right this function is no longer going to return a string let's actually make this open and close parentheses i think that's more common and then instead of returning the data random element we are going to call completion handler and then add in our data.random element i think we have to add the word self here and that's pretty much it to make this class thread safe at least for now if we had a whole bunch of different functions they would all need to be on the queue basically we were taking all of our code inside this class and putting it onto this queue uh these cues i often call them cues a lot of people like to call them locks so sometimes you'll see people refer to this as a lock let's go with that for now and now we're going to call this from our code so coming down here instead of get random data we'll say instead of calling it and returning data now we have to call manager dot get random data we have our completion handler and then we'll take our code we'll check for the title in here we'll say let data equals the title that's returned and we should be good to go i'm going to copy this and do it down here in our other view as well so now we have kind of the same situation both of our different threads running we have our thread sanitizer still active and we can run this and i'm getting an error message and that's because we need to declare the parameter as escaping so we'll use an at escaping here all right hope i'm not losing anyone i'm going to build this one more time same situation same class except this time the class is now thread safe and you'll notice when we run it that nasty nasty warning error is not showing up here right so that's pretty much the solution to making classes thread safe it is purely put all of your functions into a dispatch queue a lock or a queue and then they will be threat safe because this will basically when all the functions reach this line they will they will basically get in line here right so if you have a bunch of functions they have to asynchronously wait for the other one to be complete before the next one happens because they're all lining up inside that queue that's why this works this is much better code than what we had in our last example but of course now we are getting into actors and so more or less an actor is a class that does this automatically for us and because we are in the asynchronous swift concurrency environment we no longer have to use completion handlers either so we're going to do is copy this and paste another one down here i'm going to call this one my actor data manager still going to have a singleton this one will be in my actor data manager we're still going to have data but this time we're going to get rid of our dispatch queue instead we're going to make this an actor and we're going to revert back we're going to get out of this lock so i'm going to cut this and we don't need our completion handler and we're gonna go back to a function that returns an optional string nice and simple how we had it at the beginning uh and then we're of course going to return our random element here all right let's start using my actor data manager instead so i'm going to come down here in my home view i'm going to use my actor data manager in my browse view i'll use my actor data manager and as i've discussed in this series multiple times now when we are using the asynchronous environment we no longer want to use dispatch queues and instead we should be using tasks so what i'm going to do is just comment all this code out i'm going to comment all this code out as well so let's call manager dots get random data we can see this is an asynchronous function that returns us a string we can't use it yet because we're not in a function that supports concurrency so let's jump into a task let's call manager dot get random data that's it's asynchronous which means we need to await on it it is returning us a data so we'll say let uh maybe data we'll set it equal to get random data we can see that this is an optional string so let's just do an if let and if we get the data let's then switch back on to the main thread so we'll await the main actor.run and we will say self.txt equals the data i'm going to copy this task here paste it down here on the browse view as well and let's build our app all right we are running we still have our thread sanitizer turned on and we are still on multiple threads as we can see in these printouts but again we don't have that crazy crazy thread warning happening so what we did was basically instead of using a class that is thread safe we can now just use an actor and we can see just in this code this code is much easier to read than this code right and you can imagine if these were more complex right making sure we are always working on this queue or this lock there's a lot of room for error if you ever forget to call this uh every time you do call this you then have to add these completion handlers you have all these crazy completion handlers across your app and things get quite complicated instead we use a nice and simple actor actors are thread safe we don't have to worry about all of that and the magic of the actor is really happening in the await keyword so up here in the class every time every time the code gets to this async block it is basically being added to this queue and then waiting its turn in order to go and instead the actor since we don't have this async block when we get into the actor we are actually calling await before we get into the actor and so we are waiting to get into the actor which is more or less the same thing as this except it's super easy to write in our code we just have to await being in the actor we get to use tasks and our code just ends up being much quicker much cleaner and overall i hope you now understand just how powerful and important actors really are for a beginner developer changing the word class to actor is super simple and you might think that's all you need to do and then you can use actors but you probably don't really understand what they are doing and here we have a pure example of how an actor is solving one of the trickiest problems in any multi-threaded environment any kind of app across the board this is a very tricky problem and a very great solution all right and the final thing that i want to leave you guys with is so every time we want to access something inside the actor we then need to await to get into that actor right and that's because all of the code inside the actor is isolated it is isolated to that actor so that it is then thread safe so for example if we want to just get this data from our view if we wanted to run a function maybe we do like an unappear we can call manager.data we can see the data it is asynchronous now the actual data itself is not asynchronous but in order to get it from down here it's going to be an asynchronous task and that's because we have to await to get into the actor before we can access the data so in order to do that we would have to do something like task and then we would have to await on the data and for the data that is going to actually be shared and needs to be isolated to that actor this is a good thing this is a safety thing we are happy to await to get the data but there may be situations in your app where you have some code in your actor that actually does not need to be isolated to the actor for whatever reason now it's probably a rare case so for example maybe we have another function in here that says maybe get saved data let's just say and it's a function that returns a string but maybe instead of like accessing this shared data that is being saved inside the actor maybe it's just going to return us some new data and maybe in this function we're not really worried about thread safety because we know we're just going to get this returned new data back so the question then becomes if we want to call this function that is inside the actor but but we don't want to await to get the data how do we actually do that so right now we would call manager dot get saved data everything would work this would say our let new string we can see that we are going to get the string back but if we want to take this function and then actually make it so it is not isolated to the actor that it is in we simply mark it as non-isolated and that's why i've been saying the word isolated for the past two minutes a lot of people will write it like this where non-isolated is its own line i personally do it often like this but basically this means that this function is not isolated to the actor meaning we don't have to await to get into the actor in order to access this so coming down here instead of having to go into that instead of having to go into the concurrent environment we can just say let new string and we'll set it equal to manager dot and you'll notice already that the stuff that is in the actor that is isolated is grayed out and that's grayed out again because we're not in a because we're not in a context that supports concurrency but the get saved data function is not grayed out and that's because it is no longer asynchronous because it is not isolated to that actor so then we can use it and it's similar to creating functions we can also create constants let my random text equals something right and so if we want to access that we will call manager.myrandomtext we can see it's normally asynchronous because it is isolated but we can mark it as non-isolated as well and now we can access my random text without having to get into that actor it is important to note though once you do have something that is not isolated you can't just access the isolated functions from the non-isolated so in here if we wanted to say let data equals get random data we will run in to this problem that of course get random data is actor isolated and can't be referenced from non-isolated context so so the non-isolated keyword is pretty handy it is probably more handy when you start using global actors but we have not learned that yet because we are learning that in the next video so for now all i want you guys to know is firstly what is the problem actors solve and that is the data race problem how do we solve that problem before actors we use dispatch queues locks or cues inside the class but now even better than that we can just use an actor and actors are thread safe by default they are thread safe because we need to await in order to get into that actor and if we are ever doing something inside an actor that does not need to be isolated to the actor we simply mark it as non-isolated all right that's my video on actors hope you guys learned something if you if you did please don't forget to hit the subscribe button leave a like and leave a comment below the next couple videos are just as important as this one i hope to see you guys there in the next one we're going to cover the main actor which is the main actor which we've been using throughout this course as well as some global actors it is going to be a lot of fun i hope to see you guys there uh thank you guys for watching as always i'm nick this is swivel thinking and i'll see you in the next video [Music] you
Info
Channel: Swiftful Thinking
Views: 18,135
Rating: undefined out of 5
Keywords: Swift actors, swift actor, swift class vs actor, swift data race, swift how to use actors, how to use actors in swift
Id: UUdi137FySk
Channel Id: undefined
Length: 29min 31sec (1771 seconds)
Published: Mon May 23 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.