Introduction to Generics in Swift

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
generic code enables you to write flexible reusable functions and types that can work with any type subject to requirements that you define and you can write code that avoids duplication and expresses its intent in a clear abstracted manner hi my name is stuart lynch and in this video we're going to take a look at how you can get started writing your own generic reusable functions we'll cover the basics before we explore a couple of different practical examples now before i get started please leave a comment below if you enjoy the video and give it a thumbs up and subscribe to my channel make sure you ring the bell to be notified of new videos so if this is something you want to learn then keep watching for this tutorial i've created a starter project and you can download it from the link provided in the description below it's a playground with multiple pages and as with all my videos now i utilize a shared function found in the sources folder that'll help me keep my examples separated just keep your code for each example within the code closure and you'll see your output nicely separated in the debug pane now before we jump into more complex examples i'm going to start with a pretty standard example that is used in many explanations of generics and swift i want to create a function that prints out the sum of two numbers first let's create a function called add that has two parameters both ins in the body we can then just print the sum so we can call that function with the two parameters like 15 and 17 and execute our playground this prints out our total of 32 in the debug area perfect but what if we try that same function and use 12.4 and 15.2 as the arguments this time we get an error that cannot convert the value of type double to the expected argument type of int our argument types don't match the type specified in our functions parameters let's introduce a generic type now then first we'll copy and paste from the first example into this next code block swift can infer a type of an object that you pass in as an argument to a function we never when calling the function specify that we're passing in an end in that first case or a double as with the second swift inferred it and when it saw a double coming in when it was expecting an int it said wait a minute you can't do that what we can do is introduce the concept of a generic type meaning that the function will accept anything you throw at it and will try to deal with it as it comes in when trying to manipulate the arguments in the body of the function when you use generics you have to specify a placeholder that will represent the type it can be basically any string of alphanumeric characters but the standard adoption is to use a capital letter t to represent the type and we do this when defining our function right before we declare our parameters and we enclose it in angle brackets and then for our parameters we can replace the type with that placeholder this produces an error message however it's telling me that t must conform to the range replaceable collection protocol actually this error message isn't too helpful for us what it's saying is that in order to use the plus operator between two generic types it must conform to this range replaceable collection now if i check out the documentation on this protocol it looks like what i need is something that is an array of objects or strings which you can specify a range for or create a subsequence from in our case however we don't want to use that particular function for the plus operator we simply want to sum two numbers so instead of saying that our type must conform to the range replaceable collection protocol we want to be able to specify that we're expecting a number and there happens to be a numeric protocol for this so after we declare our type we can do the same when we are adding our conformance to a struct or class we just enter the colon followed by numeric and then the error goes away now let's copy and replace both of those functions from the first example now and paste them in here and i'll remove the comment on the second one this time when we run we don't get an error we get our result printed out whether we pass in an integer or double now what would happen if we tried to pass in two strings like add stuart lynch it won't allow us to do this because now we have a requirement that our arguments must be numeric for the next example on this page we want to create a function that will add two strings or two arrays together to form a single string or array we could call this add as well so let's just copy that function again but change the conformance this time we'll remove the numeric requirement and we get that error that tells us it has to conform to the range replaceable collection so let's do that and since we're no longer dealing with numeric values let's change our argument names to something like arg1 and arg2 now if we copy that field example from the last one and run it here we'll get our two strings joined together let's try two arrays for example add an array one two three and a second array four five six when we run this we get the joining of our two arrays now one more thing before we move on to more complicated generics what if we want a function that would accept any number of numeric items and print the sum for this we can create a function that uses generics so in this case we'll specify t again as conforming to numeric and for our function argument we can specify our numbers are a variation set so numbers colon t dot dot dot for the body of the function since we have a range we could loop through the numbers starting at 0 and adding 1 to it but a sequence of numbers has a higher order method called reduce where you can specify the starting number which is 0 and simply the operator which is plus now we can simply call this function by passing in a range of numbers like 1 2 3 4 5 and 6 and then print them out and we'll get our sum now the nice thing about functions and it really doesn't have anything to do with generics but you're allowed to have two functions with the same name so long as the parameters are different for example for our two numeric examples we have two generic types as parameters well this one has a variatic parameter this means if i copy and paste both functions into example 4 x code won't complain about it i couldn't copy in the range example because it has the same arguments as the second example now when i invoke this function i see that i have two options one requiring a range of values while the other just two of course with the second function created now this first one is redundant because we could have simply passed in two values with that variatic version too a large majority of apps require interaction with some backend server and the data provided by the server is most likely in the form of json now i have an entire series on decoding json and i'll leave a link in the notes below for the purposes of this page though i have simplified matters and i'm retrieving my json from files stored in my app bundle and in a playground we can use the resources folder for storing those files and you'll see that i have two files here one called goals.json and it's an array of json objects each with four key value pairs i've also created a corresponding source file called goal where i have a struct into which we wish to decode our json it conforms to the codable protocol so we should be able to do this similarly i have a file called resources.json that is also a json array of objects each with four properties and a corresponding file with the struct definition which is also codable note that in both cases we've made our structs and its properties public now normally you wouldn't have to do this for your projects but because in a playground the sources folder is considered a separate module you need to specify the access level as public so that your playground can access it and i have a video on this topic too if you need a refresher again i'll leave a link in the description below for the video too so back in our decoding and generics playground let's take a look at this first example i've created a pretty simple function that fetches our goals from our bundle and upon a successful decoding of the data executes the completion which is a function that has an array of goal objects that we have just decoded as the function parameter so if i want to print out the name of every goal we can run this function when we create the function we can hit enter on the completion handler and we see that we do indeed have a placeholder for an array of goal so we can assign a variable name for this say goals and then loop through the array printing the name each time executing the playground displays the correct results now this is pretty straightforward as i said and i go over how this function is created in that quotable series i referenced earlier the issue is though i want to use this same function but not have it bound to the goal type i want to make it more generic so let's copy that function into our next code block and see what we can do the first thing i'm going to do is declare that i'm going to be using generics by specifying a generic variable t for our function and replace each instance of goal with t as well this generates an error that says we require t to conform to decodable now codable is just a type alias for decodable and encodable and our two structs conform to quotable so we can safely add this conformance to our generic placeholder the error goes away but i have two other problems with this function first our url variable is named goals url and it's always going to try and decode from the goals.json file from our bundle so let's add a new function parameter that we can pass in a file name and that will be a string representing the file name without the extension and then i can change goals url to json url for bundle.main url and use file name instead of the hard-coded goals let's also change goals data to json data and make sure that we have all those references updated i'll also change goals to result and rename the function fetch data now this will work for our solution but it's always going to expect an array of our generic objects in real life your json may be one big object so instead of having an array of t for our closure argument let's just use t and we can change our decoder to just expect t this is much more generic and it'll cover the majority of cases i've simplified this somewhat as i'm assuming that the date and key decoding strategies for our decoder are the default ones and this will likely get you in trouble in a real fetch of json containing dates so please watch the decoding series for more information on that adding a key decoding and date decoding strategy won't change what we've done to make this generic however so let's test i need to pass in a string for file name so let's use resources so that we can fetch and decode our resources json now for the completion handler it's specifying that the argument must be decodable and that's all as the error says it's unable to infer the type of the closure parameter remember it can either be a single object or an array of objects that we expect so we have to tell our closure argument what to expect since we know that our resources json file is an array of objects that we want to map to a resource type we can specify our variable name as resources but also specified that we're expecting it to be an array of resource and now we can loop through that data printing out a resource resource name which is just the property resource running this in the playground we get our results displaying the resource name for each of our resources similarly we could now use this same function to get all of our goals i just need to specify that the file name is goals and that our closure parameter is expecting an array of goal and then i can just loop through all of the items printing out the goals name it is common practice especially with swift ui to provide mock data for any models you have so that you can present your views in the canvas previews this helps you to build your previews with sample data for this playground page i've added to our goals and resources struct by adding an extension for each in each case i've added a static property called mock data that's an array of the corresponding structs along with four or five array elements these variables have been made public for the same reason as before since we are in a playground and the sources folder is considered in another module now as an exercise here i've created a function that is incomplete what i want to do is to sort the goals mock data by name and then use the sorted array as the completion argument i can use the sorted method on an array to sort on one of the object's properties and i cover sorting in my higher order functions video so if you need a review i'll leave a link in the description below for that video now since mock data is a static property of the type goal we can assess it like this and perform the sorted method by comparing subsequent names now i can call that function and i can see that our completion argument is an array of goal so i can loop through the elements and print out the names now as you can see this is just specific to the goal type and we want to make this generic well we're adding more restrictions now what are the problems well we'll need to assure that our structs have a static property called mock data and also our resource model doesn't have a property called name so we can't make this sorting generic because how can we do that if our model doesn't have a name property so how do we solve this problem first let's ignore the sorting requirement and simply create a function that will return our mock data from the struct so here's what our sort mock data function would look like with that removed let's go ahead now and make it generic by adding t as our generic placeholder so again that requires specifying t and angle brackets before our function argument parentheses and we replace goal with t now this gives us a couple of errors but the first one is that t has no member mock data and this makes sense because t could be anything and what we want to do is make sure that if we're going to call this function our class or t better have some mock data now we can force this issue by creating a protocol that our structs must conform to and that protocol will specify that conformance requires a static property called mock data so in our sources folder for this playground page create a new file called protocols and in it we'll create a protocol named mockable and again once more we better make it public what we need to specify now then is that any class or struct that conforms to this protocol has a static property called mock data but we also need to specify its type and it can be anything like in our case either an array of goal or an array of resource so how can we solve this well we create a placeholder here too and since we're specifying elements of an array it's common practice to use element as that placeholder and we do this by specifying it as an associated type for our protocol and then we can specify an array of element as our type for mock data and then we also have to specify whether or not we are going to be getting or setting or both in this array now we only want to get so that's all i'm going to specify here now we can go back to each of our two structs and make sure that they both conform to mockable which they do because they both have a static property called mock data i'll add this conformance to the extension for both now you're going to get this error cannot find mockable in scope show up regularly here this seems to be a phantom error it's just xcode not catching up with save files since we made that protocol public it most certainly can be found by this playground and this error will go away now back in our playground we can specify that our function's generic type conform to this as well we're still getting an error however as it can't convert the value of an array of t dot element into an array of t so we'll need to cast this as an array of t and then because it's optional we can use if let's and complete with our now unwrapped mod data else we can complete with an empty array testing this now with resources we can see that the closure argument cannot infer the type so we'll need to be specific and specify that we're expecting an array of resource and then we can loop through and print out the resource name and perhaps the resources link or author for the resource great it's working let's just create a separator string here and check for goals too let's create a loop and print out the goal name okay now that we have this working we can finish up by looking at the sorted dilemma the problem is as you may recall that our two structs don't have any property names in common other than the id and that wouldn't be a good property to store it on what we can do though is within each struct define a default sort order and we can do this by specifying that the struct conform to comparable if you let xcode generate the stub for you you'll see that it's a simple function with the less than operator that requires you to provide what properties you want to compare so for goal we can use name if we go to resource we might want to have the default sort be on the resource property for example so again we'll conform to comparable and generate the stub then we'll do a similar thing as before we'll make the left hand side dot resource be less than the right hand side dot resource back now in playground we'll take advantage of the fact that our two structs conform to comparable and add that requirement to our generic function and we do that by using the and in our type specification with that we can now sort our data before running the completion and we do that simply by calling the simple sorted method without any arguments to specify the default and that's done at the class level as we've just defined this time when our playground runs our resources and goals are sorted by that specific property well there you have it some practical i hope examples of using generics in your code [Music]
Info
Channel: Stewart Lynch
Views: 2,019
Rating: undefined out of 5
Keywords:
Id: NY0LFoHQUbk
Channel Id: undefined
Length: 24min 13sec (1453 seconds)
Published: Sun May 02 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.