- Challenges from last class
- Structs
- Break
- Enums
- Create and use data models with Structs
- Add functions to Structs
- Use enums and their raw values
- Create enums with associated values
So far, we have learned about different types (Int, String, Array, Optionals).
Almost every time we will need to create our custom types to suit the purpose of our programs. We can create our own types with Classes & Structs in Swift.
<iframe src="https://giphy.com/embed/YiUredNQ5OdxzAaXbV" width="240" height="240" frameBorder="0" class="giphy-embed" allowFullScreen></iframe> Context for the examples in this lesson: we need to build a program that helps a Boba Tea shop to implement an "order ahead" system, because they constantly have long lines. We need to model how it will work using Swift. Eventually it might become an app.A Structure is a type that stores named properties and related behaviors. We can give a struct a name to use later in our code.
struct BobaTea {
let tea: String
let sweetness: Int
}
Syntax involves using the struct
keyword followed by the name. Everything inside the curly braces will be a property of the struct.
Properties are constants or variables that are a member of the type. Every instance of the struct will have these properties.
We'll start simple. Let's say we have a custom type and we call it BobaTea. Right now the customer can choose the kind of tea they want and the level of sweetness. Every new boba tea will need these two properties.- Create a new playground in Xcode
- Create a struct named
Coffee
that has three properties:bean
: a String that says what type of bean the coffee usessugar
: an Int that says how many sugar packets are in the coffeehasMilk
: a Bool that says whether the coffee has milk or not
var boba = BobaTea(tea: "black", sweetness: 25)
print(boba)
// prints BobaTea(tea: "black", sweetness: 25)
To create an instance, we use the name of the type with the needed parameters. This is an example of an initializer. This one makes sure that all the properties of the struct are set before we try to use them. Safety first!
An initializer is generally a method that the struct can use. But we didn't code it. Swift automatically gives an initializer for structs, using all of its properties.Create an instance of your Coffee
struct by storing it in a variable named coffee
. Make sure to provide values for the bean
, sugar
, and hasMilk
properties.
We use dot notation to access the properties in a struct.
let boba = BobaTea(tea: "black", sweetness: 25)
print(boba.tea) // black
print(boba.sweetness) //25
Dot notation will also work to assign values.
If you know you will modify the values in your struct, you should consider making it's properties variables instead of constants. The same applies when considering if the struct should be a constant or variable.
Include the type BobaTea
from the example in your playground.
Write a structure called Order
that will represent a client's order. It needs two properties:
name
of type Stringboba
of type BobaTea 😯
We can use functions to create an instance of a struct
and its properties. For example, let's write the createBoba
function that takes teaType
and sweetnessLevel
as inputs, and returns a new BobaTea
:
func createBoba(teaType: String, sweetness: Int) -> BobaTea{
let boba = BobaTea(teaType: teaType, sweetnessLevel: sweetness)
return boba
}
Write a createCoffee
function that takes beanType
, sugarLevel
, and containsMilk
as inputs (String, Int, Bool respectively), and returns a Coffee
struct based on those input parameters.
- Write a function that given input parameters returns an order type
Order
(You'll need to make anOrder
struct!) - Then create an order using the function (this should return an
Order
) - Then print it's details.
This is how I should be able to call your function:
let newOrder = createOrder(withTea: "black", sweetness: 25, forCustomer: "Adriana", includeBoba: true)
and this is what you'll print in the end:
Adriana ordered black boba tea, 25% sweetness, with boba
We know now that structures can have constants and variables. They can also have their own functions. And every instance will have access to them, to execute them.
Functions that are members of types are called methods.
What if we want to able to display the complete order of a customer every time? It might be useful to include a method inside the Order struct.
struct Order {
var boba: BobaTea
let name: String
func printDescription(){
print("\(name) ordered \(boba.tea) boba tea, \(boba.sweetness)% sweetness, \(boba.hasBoba ? "with boba" : "no boba")")
}
}
let newOrder = createOrder(withTea: "black", sweetness: 25, forCustomer: "Adriana", includeBoba: true)
newOrder.printDescription()
//Adriana ordered black boba tea, 25% sweetness, with boba
Do the same improvement in your implementation, then move your createCoffee
function into the Coffee
struct.
A value type is a type whose instances are copied when assigned.
var boba = BobaTea(tea: "black", sweetness: 25, hasBoba: true)
var anotherBoba = boba
print(boba.tea) // black
print(anotherBoba.tea) // black
boba.tea = "oolong"
print(boba.tea) // oolong
print(anotherBoba.tea) // black
Many of the standard Swift types are structures:
- String
- Double
- Bool
- Array
- Dictionary
You can confirm this by looking at the documentation 🤓 (command+click -> jump to definition)
Q1: When is it useful to define a struct
?
Q2: Which of the following is NOT a key component of a struct
?
- Name
- Properties
- Functions
- Enumerations
Q3: What do you call a function that's added to a struct
?
An enumeration is a list of related values that define a common type. It's a great way to work with type-safe values.
Enums can also have methods and properties, that makes them extra powerful.
enum TeaType{
case black
case oolong
case lavender
case chai
}
// Creating an instance
var typeOfTea = TeaType.chai
Standard practice: start each case with lowercase.
Make a CoffeeType
enum, where robusta
, liberica
, and arabica
are valid types.
var typeOfTea = TeaType.chai
switch typeOfTea {
case .black:
print("This is black tea.")
case .oolong:
print("This is oolong tea.")
case .lavender:
break
case .chai:
print("This is chai tea.")
}
Switch statements are another option to handle control flow. A switch will execute different code depending on the value of the variable given.
A few notes about switch statements
- They need to be exhaustive (check all cases or use default)
- If we want nothing to happen in a case, we write
break
- Cases can't be empty
- They work with any data type
- You can group several cases
switch typeOfTea {
case .black, .chai:
print("This is a house favorite.")
case .oolong, .lavender:
print("This is a seasonal special.")
}
Write a typeOfCoffee
switch that prints out the name of the coffee type.
We can assign a value to each case in an enum. This helps handling each case or getting a value to work with in our program.
var typeOfTea = TeaType.chai
enum TeaType : String{
case black = "black"
case oolong = "oolong"
case lavender = "lavender"
case chai = "chai"
}
print(typeOfTea.rawValue) // chai
enum TeaType : Int{
case black
case oolong
case lavender
case chai
}
print(typeOfTea.rawValue) // 3
We can use raw values to instantiate an enum.
let teaType = TeaType.init(rawValue: 2)
if let tea = teaType{
print(tea)
}
The initializer will give back an optional type. Since there's no guarantee that the raw value we're giving matches one of the available types of tea.
- Enums can have 0 or more associated values.
- These values need their own type.
- We can give them names, like external names in functions.
- An enum can have either raw or associated values, not both
enum OrderFullfilment {
case success(message: String)
case error(message: String)
}
We will handle the order result with an enum. The associated value will be a message, that we can set later when using it.
func makeOrder(order: Order) -> OrderFullfilment {
let date = Date()
let calendar = Calendar.current
let hour = calendar.component(.hour, from: date)
print(hour)
if hour < 17 && hour > 9{
return .success(message: "You can pick up your order in 30 min")
}else{
return .error(message: "We are closed, try tomorrow")
}
}
let orderResult = makeOrder(order: newOrder)
switch orderResult {
case .success(let message):
print("Order result: \(message)")
case .error(let message):
print("Order result: \(message)")
}
We used let
bindings to read the associated values.
Finish the implementation of the Boba Tea shop.
- Use enums to represent the tea types
- Add an option to customize milk too: whole, almond, oat
- Include the
makeOrder
function to practice using associated values - Experiment, break things 🤓
Find the solutions to each step of the challenges here.
Q1: Which of the following would be best represented with an enumeration?
- Names of people in a room
- Political parties
- Addresses
- Compass degrees
Q2: Which of the following would be best represented with an enumeration? Choose all that apply
- Hair colors
- T-shirt sizes
- Favorite numbers
- Car speeds
Q3: Which of the following would be best represented with an enumeration? Choose all that apply
- Car manufacturers
- Wi-fi network names
- Professional basketball teams
- Shoe brands
- Lab 4
- Stretch Goal: Look up Property Observers
- Stretch Goal: Check the documentation for Optionals and discover how they relate to today's lesson. Optional chaining
- Structs & Classes
- Initialization
- Custom Inits for structs
- Properties
- Apple Docs - When to use structs vs classes
- Apple Docs - Value vs Reference Types
- The use of self
- Article - Value vs Reference type
- Difference Between Value and Reference Type
- Discussion Questions:
- What's the difference between value type and reference type? give an example of each.
- Discussion Questions: