0:00:47.6 Tunde Adegoroye: So I'm gonna practice my Duolingo a bit and say...
"Salut, enchanté !"
0:00:48.8 TA: Nailed it. Now you all know I'm like on the first stage of Duolingo still, so we're getting there. So what I'm gonna be talking about today is Swift Concurrency. But before I get into that, I just wanna say a big thank you to the organizers for FrenchKit. It's my first time coming here and just the way everything has been run has been perfect. So if everyone could give them around of applause as well.
0:01:13.3 TA: Alright. So, enough of the nice talk, let's get to some Swift Concurrency, so... [chuckle] Just a bit about me. So my name is Tunde Adegoroye, obviously got introduced before. So I actually live in Manchester at the moment. I'm born and raised... I'm proper Manchester. If anyone has watched TV with people from the UK, you're probably wondering that's not what you sound like. Well, we do have different accents in the UK. This is one of mine and I currently worked for Bally's Interactive, where we're basically building an amazing sports product and we're launched in two states at a moment. So New York and Arizona. And we're currently trying to phase out into different states as well. Another thing as well is I'm actually a content creator, mentioned before, I run a YouTube channel called tundsdev where I teach SwiftUI and Swift Tutorials mainly, as well as I love anime, so I don't know if anyone in the crowd here likes to watch anime, but I've just finished watching JoJo's Bizzare adventure season two for Stone Ocean, which was pretty lit.
0:02:12.9 TA: I'm actually currently waiting for the Boku no Hīrō Akademia season to come out as well in October. So if anyone wants to talk about any anime, come give me a shout. I can talk about it for hours. And another random thing. You're probably wondering why is this dude on the slide. So the reason why is because I just like doing random things. So this is me in Budapest, where I'm falling to here, like a sweet potato and I'm just chilling in the ball pit. But I just like exploring new things, doing different things and really stepping out of my comfort zone as well. So, enough about me. What are you gonna get out of this talk today? Well, you're gonna get code samples and use cases of how you could use Swift Concurrency. But before I actually dive straight into the code, I just wanna do a bit of a backstory in terms of what Swift Concurrency is and asynchronous programming. So what is Swift Concurrency? Well, in 2021, Apple actually introduced new language updates to actually make our lives easier when writing asynchronous code.
0:03:08.2 TA: So what are the goals of Swift Concurrency? Well, the first goal is to make our code easier to understand. So we don't really have to deal with closures anymore that much. It's also simpler to write, so we don't just spend a lot of time actually looking at nested closure and trying to figure out what is going on. Less race conditions. You could still prevent race conditions before Swift Concurrency, but we can actually make it easier with some of the new updates that we get with the language, which I'll go through in this talk. And we can actually make the most out of threads as well. So we can actually use multiple threads a lot easier with Swift Concurrency. Now that we've gone over that, what is asynchronous code? Well, when you're writing your applications, the fundamental thing is a lot of the times is you actually have some kind of service that you're getting data and you have to display it on the screen. That's pretty much 99% of most applications. So why is this important? Well, when you're actually writing your applications, there's two types of codes that you can write.
0:04:08.9 TA: So you can write synchronous code, and if this is new to you, it essentially just means that you have to wait for one thing to finish before you can start the next task. And you have asynchronous code, which is what we are gonna be talking about mainly today. And essentially what this is, is you can have two things running at the same time and you don't really need to wait on them both to finish before you start the next task. So they're actually kind of just running in the background and you can just do whatever you want. So let's see this when writing applications. So this is just a simple application that I've just built for this demo. And you can see this looks very similar to what you probably do in your day to day job in terms of code and API and displaying data under UI. Now when you're writing code and building something like this, you actually don't want this to be synchronous because let's say you have an API that's like really slow. If I write synchronous code, I have to wait for that to finish before my user can then interact with the UI and use it. Probably not a good user experience.
0:05:06.2 TA: And we can actually translate this into real life. So if you don't know, I'm a bit of a party boy [chuckle] so I do like a night out, I like shots - don't get me around shots, I'm dangerous around shots. But let's say you went on a night out and you've finished your night out and you want to get something from a takeaway, like a takeout. So you've gone get a Kebab or a Chinese bakery or something. I don't know what the things are over here, someone's got to tell me later. But let's say you've gone to a Chinese restaurant and you now have to go into the queue, you have to wait in the queue, you have to wait to get to the front, make an order, wait for your order to come, take it home and then eat it. There's a lot of steps that you have to wait before you can actually just eat the food to recover from your hangover. Well, what about instead if you were to use an app like Uber Eats and be like this little kid? So what you could do instead is actually use something like Uber Eats to put your order in and you can be parting away and when you get home, your food will be delivered for you.
0:06:01.7 TA: So this is something that actually happens asynchronously in the background, that's sorted for you. All you need to worry about is just being at home at the time and eating your meal and you're done. So enough about this little kid! [chuckle] We're actually gonna look into some actual code samples now. So the first one we want to talk about is
async await. So a lot of you might be looking at this code and it might not be uncommon to you, but essentially what we just have here is a function that basically makes a request to some kind of service. I'm just gonna reuse this in some manager that we have. So we have the URL that we're gonna build. We have the
URLSession, which we'll use to basically send our network request. Within it, we do some checking to make sure if there's any errors. We also make sure that the response is okay and convert the data and so on and so forth. So as you can see, me explaining that it's actually not only difficult to read, but it's also a lot to write and explain as well. Just looking at this. And all we want to do is just literally just make a network request, right? I don't wanna have to do all this extra stuff.
0:07:01.7 TA: Now I don't know if anyone in the audience is, so just from my perspective, all I see right now is black. So if anyone puts a hand up, I'm not gonna see you. But I don't know if anyone can see the issue with this code as well. There's actually a bug in here. So if anyone clocked it, I've actually forgot to handle a case path here. So if we got into a situation where our decoder fails, the user could be stuck and nothing's gonna happen. Users could be looking at the app and just wondering that, "Oh, this is a really slow application", but in actual fact you just forgot to handle the case path. And there's also one more thing that you could forget as well. And that's this little bugger. Now in our career, we've all sat there for an hour wondering that, why is my API request not working? It's because you forgot to call
.resume. Now, how can we actually improve this using the new
async await updates that we have? Well, we can simply refactor it to make it look like this. So instantly looking at this, you can see that it's easier to read. We've also reduced the lines of code by 50% as well. And another thing as well is that we've actually handled all of the case paths too. So you'll see here that on some lines I'm actually explicitly saying to throw an error and I'm also using the
try keyword when I know where that function can actually throw some kind of error as well. So we are not really gonna miss any of those case paths.
0:08:22.5 TA: Also as well, there's no need to call
.resume. And the reason why is because when you're using the
URLSession version of
async await, the network request is actually executed automatically. So there might be a few keywords here that are new to you and you've never really saw before. It's cool. We're gonna go through that. So let's just break these down. So the first keyword that we have here is the
async keyword. So when you're working with
async await and you have an asynchronous function, you need to mark it with the
async keyword. Basically that's the first step you should always do. Now after you finish doing that, you also have the
await keyword. So if you want to use an asynchronous function, the
await keyword essentially tells the system that, "Hey, this is an asynchronous function that you have to wait to finish before you can continue". So it's basically just a suspension point for when it'll stop and then it will continue after you get back the response.
0:09:24.0 TA: Now, this might not be new to you, but we don't really need to use the
Result type for success and failure anymore. Instead, we can just simply just throw an error. And if we don't wanna throw an error, we don't need to mark our function as
throws anymore and we just simply just use the
try keyword when we want to try to await something that could fail. And then like I mentioned before, there's no need for closures anymore. We can just simply just have a standard function signature and just basically make it
throws and return a type. Now, if we don't want it to return a type, we just make it void simple. So this is really clear and clean to understand.
0:10:04.0 TA: Cool, so how would we use this? So, sorry guys, I'm a SwiftUI guy. So how would we use this in something like SwiftUI? Well, I've just got a simple ViewModel here that holds a collection of songs and we have an asynchronous function that simply does get songs and puts it in a published property for our UI. Now when you are, writing your code and you're building it and you see your SwiftUI, you're usually like, "Yeah, man, this works, this is great, man this is... I ain't got no problems here".
0:10:31.0 TA: But if you're used to run this on a simulator, you've now got a problem. [chuckle] And the problem is that when you're actually writing code with async await, the code, like I mentioned before, is not gonna be on the main thread, it's going to be on the background. So when you're actually writing your code, you need to make sure that if you're updating any of your UI, you place those updates back onto the main thread. So how do we do this? Well, prior to this, you may have done something like
DispatchQueue.main.async. Probably don't wanna do that with Swift Concurrency. And to be honest with you, you don't need to. And the reason why is because we have this new annotation
@MainActor. So essentially this annotation, it's just me saying that I want you to place the UI updates onto the main thread. Now, it's worth noting that I've actually put this specifically on the function. So this function wouldn't inherit that functionality. But if I was to move the
@MainActor onto the class definition, every single function would basically get that capability as well. But in this example, I've just specified it specifically on this function. Cool.
0:11:38.2 TA: So moving on. So now you all know what I listen to on Spotify. So if you've got any Beyonce fans in the building, just give me a shout. So you can see here on the screen with Spotify, we have recommendations and we have podcasts at the bottom. So how would we actually get multiple resources at the same time? Because I wanna get the recommended and I also wanna get the podcast. Well, prior to
async await, we'd have to write something like this. And looking at it, [chuckle] - like even I'm on the top and I'm looking at this now and I'm like, how am I gonna explain this 'cause I can't because we've basically got a nested closure within a nested closure to get resource and it's very difficult to read. (I'm barely being able to read this stuff on my own, right?) So how can we actually improve this using
async await? Well, what we can do is refactor it like this. So now what we're saying is that I want you to get the recommended and then after that I want you to get the podcasts and then we just got some logic here to basically convert it and display it onto the UI. So this works fine and this is actually called structured concurrency. And the reason why is because you can see your asynchronous code in a clear sequential order. So you can just see by looking at it, what's going on.
0:13:03.3 TA: But if we actually look at this... I mentioned before that we're essentially waiting for the recommended to come stop podcast. Come stop. These two are actually kind of acting like they're dependent on one another and we actually don't need them to chain because we're not like getting the data from our recommended and then passing that to our podcast to get a podcast. You know, these two things could actually execute at the exact same time. So how can we actually make this better? Well, what we can do is use something in Swift Concurrency called
async let. And what
async let allows you to do is run a number of defined asynchronous functions in parallel at the same time. So let's look at an example of this. So our function from before, that look like this, we can now refactor this to simply look like this. So now what we're saying essentially with the
async let keywords here is that this is an asynchronous function that I basically want you to run in parallel. And because these two asynchronous functions can run in parallel, we don't know when either one of them will finish. So what we have to do is actually wait for both of them to finish. So they'll run at the same time, but we will wait for them to basically finish before we continue, which is why we have the
await keyword there. And then finally, we'll just return when it finishes.
0:14:39.0 TA: So you can see, man, these are my points on Duolingo. You can see I'm doing a great job.
0:14:45.7 TA: So I'm gonna use Duolingo here because I feel like it's the best app for learning a new language that I've come across. And what I wanna do here is look at an example where if we wanna fetch images. So you can see here that I'm trying to learn, French, Japanese and Spanish, which is a complete lie. (I'm only doing French). But what about if we wanted to try to get these images? So got a bit of a problem here because before we can't really use
async let. And the reason why is because like I said before, we said that we need to have a defined number of asynchronous functions, here I could be learning one language, two language, three languages. I don't know how many people and how many languages they're learning. So we kind of need something a bit more dynamic to actually fetch these images. So how can we get around that? Well, what we can do is use something called a Task Group. And a task group simply allows us to execute an undefined collection of asynchronous tasks concurrently in parallel. Now when you're working with task groups, they're essentially just like a group of child tasks that you have to wait for all of them to finish before it returns a value. So with a Task Group, it holds a collection of child tasks. They're also independent as well because they run in parallel at the same time.
0:16:08.0 TA: There's no order. So if I was to execute a child task first, that could actually finish last or in the middle. We don't know because there is no order. Now one thing to be worried about when you're working with task groups as well is that you have to wait for all of the child tasks to finish before it can return a value. So if you're someone who's working on an application that like fetches a whole bunch of images, like 50 or a hundred, probably a task group is not the right way to go because you don't want to have to wait for 100 images to download before it returns a value: that's gonna be really slow. I'd probably advise using task groups when you have a small collection that you want to basically fetch dynamically, like in my example where you can only learn more than maybe five languages maximum. And also with Task Groups they can have a returner value be void or throw some kind of error as well. So it depends on what it is you wanna do. So what we're gonna do now is actually look at our example from before and we're actually going to basically break down how we'd actually implement something like this with our Duolingo sample.
0:17:11.1 TA: Looking at this function, the first thing we need to do is we actually need to define the signature for the function. So I've said here that it's going to be
async throws and it's going to return an array of pictures that get displayed on the UI. We then have code where we actually use
async await. We basically have code where we use
async await and we essentially does fetch an array of URLs that we're going to fetch our images from and we're gonna store that within our photos that we'll return at the end. Then we'll actually have our Task Group as well. So I've defined here a throwing task group, but if you don't want your task group to throw, you can just have a normal task group that doesn't throw. So notice here with the task group as well, I've had to use the
await keyword because we have to wait for everything to finish and I use the
try keyword because it could throw some kind of error. I've also defined the type that each one of our child tasks will return as well. So I'm saying each one's going to return a
Picture. Now is also worth knowing as well that if you don't wanna return anything, you could actually put this as void. It's up to you now, where the fun happens.
0:18:24.0 TA: So within here, this is where we're now creating our child task within our task group. So within here, we're basically just getting our images. So we're fetching them using an asynchronous function and then we're going to return that within the child task that we're creating. Second to finally we're going to loop through each one of the child tasks and append that into our photos array. And then we're simply just going to return an array of photos. So breaking that down step by step, you can see that this function now allows us to basically get an array of dynamic images from some kind of service quite nicely.
0:19:05.0 TA: So we've saw how we can actually do that using a task group. But in our application, how would we handle caching? Because I don't wanna have it where a user goes into the profile screen and every time I have to fetch the images again, like that's bad user experience. So we actually need somewhere where we can actually store our images. Now what we could do is we could use something like this, which is, I know there's gonna be some people booing 'cause I use a singleton.
0:19:30.3 TA: But, we could do something like this where we have a singleton that is like a cache for our images and we're using
NSCache to basically just store the pictures within it. But there's a problem with this. And the problem is, is that remember what I said before about using a task group, we don't know if a child task is finished because you could actually have multiple ones running in parallel. Also as well, how can we make this thread safe? Because if we actually have two child tasks running at the same time and they both try to write to this cache, we've now got ourselves a 'data race'. So how do we stop that? Well, what we can do is use something called an
actor. And an
actor essentially allows us to protect our state from data races. So it's a nice way for us to basically protect our mutable state across different threads. Now you might be wondering, "Oh, so what's Apple done to make this a lot easier for us to use?" Well, all we need to do is change the keyword
actor and that's it. So now by making that small simple change, we now have a thread safe way to write to this image cache. Now how will we use this cache with the function that I was talking about before?
0:20:44.2 TA: Well, we can just do some simple dependency injection. So with the function that we have here now, all I need to do is just simply update the parameters and we now have a way to inject our cache into that function. So we pass it in and in order to use it, we just simply need to say, "Hey, I want you to safely write to this cache with the data." So we use the
await keyword. So you can see here now we have a thread safe way to write to our cache. Cool. So I spoke a lot about
async let, task groups, all this other stuff. But when you're actually using Swift Concurrency, it all comes down to you have to execute your code within something called a
Task. So you might be wondering what is a task? And the reason why I left this last is because this is essentially what you need to use to basically kick off these asynchronous functions. So a task allows you to essentially execute asynchronous code in Swift Concurrency. Now when you're working with tasks, you can actually control the priority of them. So I could say I want this task to be user instantiated, background low, whatever it is I may want it to be. You can actually control the relationships in terms of how they interact with each other as well.
0:22:03.2 TA: So you can actually have like a task group, a parent with a bunch of child tasks or you could have independent tasks that just run without actually having no knowledge of each other. It's up to you. And another important thing as well is cancellation. You can actually handle cancellation. Now when you're working with SwiftUI, you basically get a
.task modifier that handles this for you automatically. But if you are working in a non-asynchronous context such as like a view model or something and you want to execute a task, you kinda have to handle the cancellation yourself. And we'll look an example of that in a second. So what I wanna do is just show you two examples of how tasks work with the life cycle. So this is an example of where everything's hunky-dory, if you know what that term means. And everything's gone, okay. So we have our app and we're just gonna trigger an asynchronous function within a task. So that function is gonna be called
0:23:07.6 TA: Now, within
fetchContent we actually have another asynchronous function called
fetchUsers, that is actually going to return some kind of value for us within the parent function. Now, this function is going to actually finish with no issues. And because it finishes with no issues, our app gets the data successfully returned to the app. So it's all good. But what about an example when things go wrong. So, let's look at a failure example: so we've got our app, we do the same thing again with our parent and then we have two child tasks within it. So,
fetchPosts. Let's say if
fetchUsers fails and throws an error, what's gonna happen to
fetchPosts is actually going to get a cancellation error and it's actually going to cancel as well. And also, any child asynchronous functions within that that were executing as well will also throw a cancellation error as well. So what will happen is our app will actually get a cancellation error to tell us that something has been canceled and gone wrong.
0:24:20.6 TA: So how will we actually use a
Task? Well, like I mentioned before, in SwiftUI, you have the
.task modifier where you can actually tell it to fetch some code, some basic execute an asynchronous function.
0:24:34.0 TA: But in a non SwiftUI world, you have to actually do this. So you have to actually use the task closure to execute an asynchronous function. And because you're doing it like this as well, you need to manage your own cancellation, because let's say for example, you're on a screen where you actually execute an asynchronous task and it's downloading and someone hits back: you don't wanna keep on executing that asynchronous task. You actually wanna capture this task and then cancel it when someone goes off the screen. So that's something that you want to keep in mind.
0:25:05.0 TA: So for some people here you might be wondering, we use Combine or pure vanilla if we can't use it: we support iOS 14. Well, you can actually add supporting and starting with Xcode 13.2, you're actually able to actually add in Swift Concurrency to your projects as well.
0:25:23.6 TA: Another thing you can do is actually use something called a continuation. So with this example here, I've actually got two implementations of the same function. So we have the closure based example at the top. But if you actually look at the second example, I've created a continuation here. And what our continuation allows you to do essentially is to bridge the gap between your old legacy code to your new existing Swift Concurrency implementation. So you can slowly start to deprecate any code that doesn't conform to Swift Concurrency. So we've got our continuation, and in order to use it within our closure, all we need to do is just simply to say on the
.resume return this value and if it fails I want you to throw an error.
0:26:10.6 TA: So what are we covering today? So we covered all of these topics. So it's quite a few topics that I've actually spoken about in a short amount of time and that's only a small bit of it 'cause there actually is a whole lot more that I could have spoke about, but I just don't have time for it. So if you wanna learn more about Swift Concurrency, I actually do have a course on my YouTube channel here, on tundsdev, which is a free course that has code samples and snippets of all the stuff that I've spoken about today. Or you can just come and chat to me and I can just talk to you about it as well later.
0:26:39.6 TA: Another thing I wanted to note as well quickly is we also have this new package available to us called Async Algorithms, which if you are someone who works in a Combine world, you can start to migrate those operators to Swift Concurrency as well, which is pretty nice. And also at WWDC as well, there was also this video as well, which actually shows you how to make the most out of your concurrent code.
0:27:01.6 TA: So you may be wondering, "Okay, I've got a brand new clean slate in life, I can do whatever I want. What do I do with this project? Do I start in Combine then in Swift concurrency?" In my opinion, I would start using Swift Concurrency if you could. Now obviously if your company doesn't let you just add Swift Concurrency or you just don't have that as a Jira Ticket, then maybe start using continuations to just slowly start to migrate if it's something you wanna do. And if you enjoy Combine, use Combine, it's up to you.
0:27:31.2 TA: So a few final things is, this is actually my YouTube channel. So if you ever saw this logo before and thought about what do I look like in real life, now you know. I don't walk around with my tongue sticking out of my mouth all the time. And here's also a link as well, so you can subscribe to my channel. I also do have some free course on my channel for anyone who's interested about how to get started in SwiftUI. So a beginner course. I also have a take home test course, I've just finished how to manage data flow and also MVVM examples as well that you can check out on my channel. Now, surprises, Antoine kinda stole my thunder with the badges man, but I have stickers, so if you wanna and come and get a laptop sticker, just come and talk to me in the break area and I can give you a sticker in the most non-dodgy way ever. I'll try not to pull out my back pocket and make it like I'm giving you something else.
0:28:18.1 TA: And then finally, well, I was gonna say merci and au revoir, and I appreciate everyone for listening to my talk. I don't think I have a lot of time for questions, but if I do have a lot of time you can ask me questions in the foyer a bit. But I hope you enjoyed my talk and you enjoyed what I spoke about. That's it.
0:28:41.1 Greg: Thank you, Tunde.
0:28:41.7 TA: Thank you so much.