How do they do it?
The Cardinals establish a culture in St. Louis that they duplicate all the way down to Rookie ball in Johnson City, Tennessee.
In our last post we discussed how we use structs to model data. We can also use classes to model data. But what are classes and when do you use classes instead of structs? Let's walk through those questions and, along the way, use baseball's minor league system as a coding example.
What is a class?
Big Nerd Ranch's Swift Programming notes that like structs, "classes are used to model related data under a common type."
And here's what classes do that structs cannot which Apple's Swift documentation points out:
- Inheritance enables one class to inherit the characteristics of another.
- Type casting enables you to check and interpret the type of a class instance at runtime.
- Deinitializers enable an instance of a class to free up any resources it has assigned.
- Reference counting allows more than one reference to a class instance.
Let's flesh those out some more.
- Inheritance enables one class to inherit the characteristics of another. This is particularly helpful if we want to create classes for baseball teams as each major league team has a number of minor league affiliates under them that are similar, but different. For instance, once we create a BronxYankees class with such properties as wins, losses, and attendance, the AAA ScrantonYankees can inherit all those properties from BronxYankees as they'll need them too, and then add a property that is unique to Scranton such as, DarrylPhilbinBobbleheadNights. We'll code out an example shortly.
When deciding whether to use a struct or a class, ask yourself if you think you'll need to use inheritance or not. If you are sure that you will not need inheritance, a struct is probably all that you need. If you know you'll need inheritance, create a class. If you are not sure if you'll need inheritance in the future because you can't tell the future and aren't sure how what you're building is going to change over time, choosing class over struct will do a better job of covering your butt and making your life easier for the long run.
Why?
Because if you choose struct now, but then realize down the road that you now need a class you will have to go back and change all your struct files and where they are called in your main file which is, technically speaking, a pain in the ass.
Now back to what classes offer that structs do not:
Why?
Because if you choose struct now, but then realize down the road that you now need a class you will have to go back and change all your struct files and where they are called in your main file which is, technically speaking, a pain in the ass.
Now back to what classes offer that structs do not:
- Type casting enables you to check and interpret the type of a class instance at runtime. Why is this important? For a few reasons. First, type casting allows you to check the type of an instance. Once you know what type an instance is, whether it is a String, Double, or a custom type, then you know whether it handles integers, floating numbers, strings or a custom type. Type casting also allows developers to know what protocols the instance conforms to according to its type. In other words, once you know what type something is you'll know what you can do with it. In baseball terms, if you are a hitter and you know what pitches the pitchers throws, you'll have a better at-bat.
- Deinitializers enable an instance of a class to free up any resources it has assigned. Specifically, deinitializers free up memory resources which means your program can run faster and more efficiently. A full description of deinitialization is available here in Apple's documentation with a working example included, but, in short, deinitializers remove set values from instances of the class you created when they are no longer needed. In baseball terms, initialization is like a player being given a uniform and number when he first joins the team and deinitialization is like when he hands in his uniform to the team before retiring.
- Reference counting allows more than one reference to a class instance. Earlier we created a BronxYankees class from which the ScrantonYankees inherited properties. In this case, ScrantonYankees are an instance of the BronxYankees class. Thanks to reference counting not only can ScrantonYankees inherit from BronxYankees, but so can all levels of the Yankees' minor system (TrentonYankees, TampaYankees, CharlestonYankees, StatenIslandYankees, etc).
Open Xcode and double-click on "Create a New Xcode Project". Choose macOS as your template and for this project choose "Command Line Tool" before clicking "Next". Give your project a name such as, YankeesSystem, and make sure that the language is set to Swift before hitting "Next". Then choose where you want to save your work. It can be in a specific folder, on your desktop, or wherever else you want; hit "Create".
Xcode will then open. On the far left panel click on the "main.swift" file which will open in the center panel with some sample code in it some of which we'll remove in a minute.
Create a new file (File > New > File) and name it BronxYankees. It will save under the main.swift file in the far left panel as BronxYankees.swift. Enter the BronxYankees.swift file by clicking on it.
Let's create our class and define what properties it will have.
For this project and this BronxYankees class, we'll start with attendance, wins, and losses and give them all initial values.
Next let's make them do something. Specifically, let's have them print out, "The Yankees have 72 wins, 40 losses, and a current attendance of 2000000."
First, we need to add a function to our BronxYankees.swift file. In this case, it is our seasonUpdate function that plugs in the wins, losses, and attendance values thanks to string interpolation.
To make this function run, we now go back to main.swift and call the seasonUpdate function.
On line 11 we call the file where the BronxYankees class resides and set it to a new variable called bronx. On line 12 we call the seasonUpdate function from the BronxYankees file. This returns:
which shows up in the console.
If we want to recreate the Yankees farm system we'll need to setup the Yankees' AAA affiliate in Scranton. Now let's create a subclass that inherits the wins, losses, and attendance properties that we created for the BronxYankees class. To do this we need to create a new Swift file. Let's call this new file ScrantonYankees. This is what that will look like:
On line 11 we create the ScrantonYankees class and we make it inherit from the BronxYankees class (ScrantonYankees: BronxYankees). By doing that we don't need to create another round of wins, losses, and attendance variables: the ScrantonYankees class automatically inherits them from the BronxYankees class.
But the Bronx is the Bronx and Scranton is Scranton. They may have wins, losses, and attendance in common, but they are not exactly the same.
On line 12 we show how Scranton is different: Scranton has Darryl Philbin bobblehead nights at their games (I'm just making this up).
But what happens if we call the ScrantonYankees seasonUpdate function? This prints out to the console:
This is nice, but it just prints out the BronxYankees wins/losses figures before printing the ScrantonYankees property we created. But what if Scranton actually has 38 wins and 22 losses? How do we change that?
It takes a few steps, but we can do it. Let's go.
First, go into the BronxYankees.swift file. After the seasonUpdate function we are going to add three new functions that allow us to change the values of our attendance, wins, and losses properties. They will look like this:
Let's walk through these. First, use the func keyword to create a new function. Then we give the function a name, in this case, we use changeWins, changeLosses, and changeAttendance - all names that indicate what the function is going to do, that is, change the values of our different properties.
We then pass an argument (amount: Int) that indicates that what we will pass will always be an integer type. Then we drop a { to announce that we are about to define our function.
Within our curly brackets we define our function. In this case, we are saying, "the number of wins/losses/attendance will change by the integer amount that we pass as an argument."
When done with the function we close it up with a }. One big step done, two to go.
But, you say, why can't we just go back to the main.swift file, invoke changeWins and changeLosses, plug in our integer arguments and be done with it? Good question. Let's try that and see what happens.
On lines 12 and 13 we told our program to replace the BronxYankees' 72 wins and 40 losses with 38 wins and 22 losses for the ScrantonYankees. But as we can see in the console, the program added 72+38 and 40+22 instead, which is not what we wanted.
Fortunately, we can fix this by initializing our properties in the BronxYankees.swift file. Here's what that looks like:
Line 16: We use the keyword "init" to announce that we are going to initialize our properties. We then pass attendance, wins, and losses as arguments and state that they are all going to be of the integer type. We then open up some curly brackets where we'll define our initialization function (lines 17-19).
"Self" in Swift is not an easy concept and can get meta pretty quickly so what I will say here is this: On lines 12-14 we have attendance, wins, and losses for the BronxYankees. On lines 17-19 we have attendance, wins, and losses for other, future instances of BronxYankees subclasses whether that is for the ScrantonYankees, the TampaYankees, etc.
To that end, lines 17-19 say, "the value of our attendance, wins, and losses properties will be equal to what we assign them in the arguments that we pass, not the initial values assigned to attendance, wins, and losses for the BronxYankees."
It is important to note that Swift initializers do not return a value as our example above shows. There are no values there. Rather, they serve to prepare instances of a type for use later on which is what we'll do next.
First, let's go back to our main.swift file. Once inside that we need to call on our ScrantonYankees subclass (line 11) and then call the seasonUpdate function (line 12).
Next we have to pass our attendance, wins, and losses arguments and their associated values to ScrantonYankees. While it's true that attendance has nothing to do with our final statement, we cannot run the program without giving attendance a value. As you can see in the console, it doesn't show up anyway.
So, as usual, a discussion on one topic, in this case classes, spilled over into other subjects as they all work together.
On Deck: The discussion on classes and structs continues in our next post which will go over the difference between reference types and value types, another important concept to understand when deciding how you want to build your programs.