Now, in the last lesson, we spoke about the theory part of structs versus classes and we talked about value 1
types versus reference types. 2
Now, in this lesson, I want to show you using real-life examples just what all of that means. 3
So we're going to create a new project inside Xcode so that we don't mess up our calculator app and 4
you're going to head into the mac tab and choose the Command Line tool, and go ahead and click Next. 5
Now, I'm going to call this simply just Structs vs. Classes 6
and I'm going to save this somewhere on my desktop. 7
So we've worked with Mac 8
Command Line Tools before 9
and you know that there is a Main.swift which is the entry point and all of the code here gets called 10
in sequential order, 11
and this kind of structure allows us to create multiple files that we can tap into, 12
so I can show you the differences between structures and classes. 13
Now, I'm firstly going to delete that print statement and I'm going to create a brand-new Swift File 14
inside the same folder and it's going to be called ClassExample. 15
Then I'm also going to create something called StructExample. 16
And now I have my two files where I will show you what structs and classes do. 17
So, now we can head over to our class example and we're going to create a brand-new class. 18
Now, this class is going to be called a Superhero 19
and it does not inherit from any other superclasses. It's just an independent class. 20
Now, this class superhero is going to have two properties. 21
One is going to be called name and it's going to be a String and the other is the universe where the 22
Superhero is from, 23
and this is also going to be a String. 24
Now, at this point, we're pretty used to this error which says, "Class 'Superhero' has no initializers," are two 25
stored properties name and universe, 26
both don't have any initial values. And swift, 27
remember, always needs initial values even if it has to be nil. 28
So in this case, I'm not turning any of these into nil by turning this into a optional String. 29
No, I'm actually going to initialize it. So to initialize it, we can write the designator init, 30
and in this init, 31
so whenever we try to create a new object from the Superhero class, we're going to provide a name that's 32
in the form of a String and the universe in the form of a string. 33
Now, when we initialize the class with those values, then we're going to set this class' name property to 34
equal that local name variable, 35
and we're going to do the same with our universe. 36
Now, all of this should be familiar to you because in the intermediate Swift classes, we spoke about designators 37
initializers, convenience initializers, and what it means to create new objects from classes. 38
So if you need a brief refresher, feel free to head over there and just revise those sections before 39
you come back because I'm going to assume that you've understood how to initialize a class. 40
So, now that we've created our class, then we can quite easily, in another file, create an object from that 41
class. 42
So let's create a constant called hero and it's going to be equal to a new instance of a Superhero. 43
And when I tried to initialize it, you can see that the initializer requires that I give those two stored 44
properties a value. 45
So let's call it, I don't know, "Iron Man," 46
and he's from the "Marvel" universe. 47
Okay, so this is how I've created my class and this is how I've initialized my object from that class. 48
Now, if I was to go into my StructExample and create that Superhero class as a struct instead, then I 49
would simply replace the word class with struct, and the struct also has to have a name that's capitalized. 50
Now, if I tried to call this Superhero as well, then you can see that Xcode gives me an error and it 51
says, "Invalid redeclaration of 'Superhero.'" 52
Now, even though, in this case, this is a superhero struct and, in this case, this is a Superhero class. 53
The Swift compiler doesn't actually let you get the same name to struct classes or enums, so I can't 54
create a enum called Superhero either. 55
This is all forbidden, so I have to give it unique names. 56
So let's change our class Superhero to be called ClassHero, 57
and, of course, we have to change it here as well. 58
And then, inside our struct, I'm going to call this a StructHero, 59
and now all of our errors will go away and we can continue creating StructHero. 60
So, again, this struct is going to have two properties, 61
name which is a String, and also universe which is also going to be a String. 62
Now, in this case, you'll notice that no matter how much I try to refresh or command B or run, you can 63
see that I don't get any errors, even though I also don't have any initializers inside my struct, whereas 64
with our class if I didn't have these few lines of code, then Xcode very quickly starts screaming that 65
ClassHero has no initializers because these two stored properties need values and, therefore, I need 66
initializer. 67
Well, in structs, you actually get a free initializer. 68
So down here, if I try to create a new hero that's a new instance of the StructHero, you can see that 69
as soon as I tried to initialize it, I get an initializer, 70
and this is created behind the scenes every single time you create a struct which makes life just a 71
little bit easier for you when you're creating these structures. 72
So, now that we've seen how you can initialize structs and classes, 73
I'm going to delete these two lines of code, 74
and we're going to head into our main.swift. Now, over here, I'm going to create a new hero which is 75
going to be a ClassHero and I'm going to initialize it as "Iron Man" from the "Marvel" universe. 76
Now, at this point, if I needed another Marvel hero, then it might seem that I could just say anotherMarvelHero 77
is equal to the hero. 78
And I make a copy of this hero which is initialized to have Iron Man as the name and Marvel as the universe, 79
and then all I do is I change my anotherMarvelHero and I only change its name, and I call it "The Hulk." 80
Now, because our hero already has Marvel set as the universe, then my anotherMarvelHero, just by copying 81
over that hero, 82
then it seems like I would have that same universe already, 83
and all I need to do was just to change its name. 84
Now, if we go ahead and do some print statements just to see what those values are, 85
so let's say, here name equals, and we'll use string interpellation to tap into hero.name and 86
we'll also at the same time print "anotherMarvelhero name is equal to anotherMarvelHero.name. 87
So let's go ahead and run this. And I want you to predict what will be the results for each of these 88
print statements. 89
All right, so that is the result that we got back. 90
We've got hero name = The Hulk, anotherMarvelhero name = The Hulk. 91
Wait a minute, there has to be something that's wrong here, right? 92
I didn't change the name for hero. 93
I never said hero.name = The Hulk. 94
What is this all about? 95
This is not what I predicted at all. 96
Well, this is a good example of a reference type behavior. 97
Now, when I made a copy of hero and I assigned that copy to something called anotherMarvelHero, 98
what actually happened behind the scenes is I simply created a new reference to the same object. 99
And when I changed a property on my new reference, it also changed the property of that same object which 100
both anotherMarvelHero and hero refers to. 101
Now, remember how we said that structs were value types and classes were reference types. 102
And when I create a new copy of a struct, I just copy over the values to a new thing. 103
But when I copy a reference type, then I simply create a new reference to the same thing, 104
and the actual object is not copied at all. 105
There's still only one blue folder that contains all of that data. 106
So whereas with the structs, when we make a new copy of the struct, we end up with two copies each having 107
separate values that can be changed separately from each other. 108
But when we copy reference types such as classes, then what we end up doing is we don't actually copy 109
the object with all the data. We just copy the reference to the object 110
and this means that we now have two references to the same object. And when we tried to change a hero or when 111
we tried to change anotherMarvelHero, they're both changing the same thing 112
and it can get very confusing very quickly. 113
You can imagine that as your app gets more complex, you can end up with objects that are reference from 114
lots of different places all trying to modify the same object, and you will end up with really unexpected 115
behavior and you will lose track of which reference is changing which object, and end up with a whole 116
lot of complexity and an app that's very difficult to debug. 117
Now, whenever I think about this problem of multiple references to the same object, I think about Black 118
Friday sales and people fighting each other for the same objects all trying to grab it and buy it. 119
And as you can see, there's a lot of mayhem that ensues. 120
So if we head back into our code where we show that unpredictable behavior previously, whereby changing 121
anotherMarvelHero's name, we actually ended up changing the original hero's name as well. 122
Well, let's go ahead and change this hero from a class hero to a StructHero using our struct blueprint in 123
the struct example file. 124
Well, now when we create a copy of the hero 125
that's called another Marvel hero, 126
when we change the name of anotherMarvelHero to "The Hulk," it won't affect our original hero's struct. 127
So let me press play and show you what the results looked like. 128
You can see that our hero name is Iron Man, just as we intended, and our anotherMarvelHero name is "The Hulk" 129
just as we intended as well. 130
And this is why structs are much simpler 131
which means that it's easier to create a larger app with less complexity. And you can imagine that if 132
you're coding on the same project with lots of different people or sometimes even if my code with me 133
and me in six months, me in six months is like a random person. 134
She doesn't remember how the code works. 135
She doesn't remember which things can be changed, which things shouldn't be changed, what the effects are, 136
and so we have to safeguard against all of that. 137
We have to try and make everything as least error prone as possible, so that you guard against other 138
people or future you messing up your code. 139
Now, here's another example of when reference types can really hurt you. 140
Let's say that I create a new hero initialized from our ClassHero again and I create a new array called 141
avengers. 142
Now, inside my array, I'm going to include my first hero as well as my anotherMarvelHero. 143
So, now I have an array with two class hero objects. 144
And at some point, I realized that actually my avengers, I'd really prefer the first avenger to be "Thor" 145
instead. 146
So I know that I've created this brand-new Avengers array with some heroes inside, but I'm going to change 147
that array's first item to have a different name and call it "Thor." 148
So, now if I go ahead and print my "first avenger name," now what do you expect to happen? 149
Did you expect that all three heroes now all have the name Thor? 150
Or did you expect that we'd be changing the actual array, rather than all of the referenced objects? 151
Well, in this case, our avengers, it's actually an array of references. And whenever we change anything 152
about our array, we're actually not changing the array. We're changing the underlying object which is shared 153
between this array, this object, and this object. 154
And so now all three of them have the name Thor. 155
Now, again, we can fix this by simply reverting back to a simple struct. 156
And in this case, everything that we assumed is now exactly what happens. 157
Our hero is still called Iron Man and our anotherMarvelHero is now called the Hulk, 158
and our first avenger's name is now Thor. 159
And this is one of the biggest benefits of struct. 160
They make everything a lot more predictable, a lot simpler, 161
instead of having these references all over the place pointing to different objects that sometimes might 162
collide, and you might have a different class that changes your objects without you expecting it, too. 163
Well, with struct, 164
it's a lot more straightforward and it's a lot simpler. 165
Now, another problem with class objects is how we deal with immutability. 166
So I'm going to delete all of this code 167
other than our initial initialization code. And I'm going to create a new instance of a class hero. 168
But instead of creating it as a variable, I'm going to create it as a constant. 169
So now I have this constant which is called hero which is of type 170
class hero. 171
Now, what do you think will happen when I try to change the name property of my constant hero? 172
So we all know that constants get a value assigned only once and they cannot be changed, 173
right? Well, then why is it possible to do this? 174
And I'm not getting any errors at all from Xcode, this is perfectly legal to do. 175
Well, the reason is because in our ClassHero class, you can see that we created name and universe as 176
properties that are variables. 177
And this means that even though we create a constant called hero, the only part of it that's constant 178
is that we can simply reassign it to a new class hero object. 179
We can't do this, right? 180
This is what the "let" is preventing us from doing. 181
But it does let us change everything else about our object hero. 182
I can change the name. 183
I can change the universe. 184
Now, that's not right, 185
right? Now, the whole reason why we create things as constants is to make it immutable, 186
make sure that the future me or somebody else on my team can accidentally change something that shouldn't 187
be changed, 188
right? 189
So this is another reason why you might use structs. Because if this was a struct instead, then we will 190
get true immutability. 191
And you can see that Xcode now gets angry even when we try to change one of its properties because hero 192
is a constant now and it has true immutability. 193
It cannot be changed after it was created because it has that "let" keyword, 194
and also because the underlying thing is a struct. 195
And this is because a struct, remember, is a value type. 196
So when you change it, you actually have to create a whole new copy, and that "let" keywood is not letting 197
us do that. 198
So none of it can be changed if it is declared as a "let" constant which is, again, kind of what you would 199
expect, 200
right, 201
when you make something immutable. The previous behavior when we were able to change lots of things about 202
our hero without any warnings at all can potentially lead to a lot of unexpected errors. 203
Now, the last thing I want to show you about structs is if inside our struct we created a function and 204
it's called reverse name, 205
and this function returns a String, and what it does is it takes the name, so self.name, and it calls 206
the function reverse, 207
and what this does is it will return an array containing the elements of the sequence in reverse order. 208
So that means that if our name was Hulk, then what we'll get back is K-L-U-H. 209
But the only problem is that this returns a collection, a reversed collection, and it's not a String, 210
so we have to cast it into a String if we want to be able to return this as a String. 211
So, now if we head over to main.swift and we say, hero.reverseName, and let's go ahead and print 212
the output of this function call into our debug, and you can see we get "naM norI" which is the reverse 213
of Iron Man. 214
Now, so far so good. But what if our method inside our struct was trying to change one of its properties instead? 215
So let's get rid of this output and make this function rename self,name to equal self.name.reversed. 216
Now, when you try to change a property inside a struct, Xcode will give you an error, 217
and this is because whenever you try to change a structure, you have to create a new copy. 218
And so if your method is going to change something about the structure, say, one of its properties to 219
have a different value, then we have to mark this function that has that capability with the keyword 220
"mutating." 221
And this tells the Swift compiler that, Hey, watch out, this method is capable of changing the structure." 222
So that means that when this method is run, we need to create a brand-new copy of this structure, and 223
this is why you see this keyword "mutating" all over the place 224
when you start working with struct. So structs are really, really cool and they're very, very useful, and 225
the simple things that are predictable which will make your code less prone to errors and to bugs. 226
So I recommend that whenever you're creating a new data model to go for a struct, instead of a class, 227
if you want to create a better architecture for your app. 228
Now, there is just one thing that struct can't do and you have to be aware of, the can't subclass. 229
So, for example, if I wanted to create a struct that was a subclass of an NSObject, for example, then I 230
will get a error: Non-class type 'StructHero' cannot inherit from class an 'NSObject.' 231
You don't get inheritance from struct. If you need inheritance, 232
you have to make a class. 233
And this also means that when you're working with Objective-C objects and you want to be able to subclass 234
them or work with them, then you also can't use struct because in Objective-C, the structs are very different 235
to the Swift struct. 236
The structs in Swift is almost like an eight-legged insects, whereas the struct in Objective-C is kind 237
of like an insect with just one leg left, 238
it doesn't really do very much. 239
So what you should remember from this lesson is that a struct is a value type and that means that it's 240
copied whenever it's used. 241
So it's equivalent to say if we were working on a presentation together and we've got a template that 242
we're working off, I give you one copy of the template presentation and I have one copy of the template 243
presentation. We work on it alone and independently at home 244
and that means that my edits to that copy will not affect your copy and vice versa. 245
Now, if we were working with classes instead because classes are a reference type, then whenever they're 246
passed around or whenever they're used, say, inside an array or passed as an argument in a function, 247
whenever you try to use a class object, then you're creating a new reference to it. 248
And when you edit it or when you change it, you're always changing the same underlying object. 249
And this is equivalent to us working on a presentation together and we've only got one printed copy. 250
So what that means that if you decide to scribble a mustache over my headshot in the presentation, well, that 251
is what we're going to present at the end. 252
All of your changes affect mine because we're all referencing the same object. 253
So in terms of the differences between structs and classes, structs are simpler, they are faster because they're 254
stored in the stack, rather than the heap, 255
and you can create deep copies of structs 256
that are not just references to an object. 257
They have true immutability. 258
So when you declare a struct as a let, then you can't change any part of the struct including its properties 259
and methods. 260
And finally, you have less memory leaks and thread safety as well which we're going to go into when we 261
explore more topics, such as memory management and multithreading. Now, in comparison, classes, they have inheritance, 262
and this is the biggest thing that's going for them because inheritance is core to object-oriented programming, 263
being able to inherit and subclass is what makes OOP so powerful. 264
So when you need inheritance, then you'll need to use classes. And, also, classes will allow you to work 265
with Objective-C code. 266
And a lot of the API for the iPhone is still in Objective-C. 267
So when you need those components that are written in Objective-C, then you can't interact with them 268
using a struct. 269
So the official Apple advice, which was updated only a few weeks ago, and this is apparently their final 270
word on this topic, is that you should use a struct by default whenever you want to create a new custom 271
object, and only turn it into a class when you find that you actually need inheritance or you actually 272
need to be able to work with Objective-C code. And then, in that case, turn the struct into a class. 273
So in certain cases, you still need to rely on classes, but start with a struct and only go up if needed. 274
This is exactly the same as the advice for Swift access levels. 275
Start with the least inclusive, most private access level, and only increase its capability as and when 276
needed.