Skip to content

Latest commit

 

History

History
578 lines (391 loc) · 16.1 KB

File metadata and controls

578 lines (391 loc) · 16.1 KB

Structs, Classes & Enums

Agenda

  • Challenges from last class
  • Structs
  • Classes
  • Break
  • Enums

Challenges from last class

Compare your solutions with the person sitting next to you.

Learning Objectives

  1. Understand the differences between Classes & Structs in Swift
  2. Create data models with both Classes & Structs
  3. Know when to use enums
  4. Create enums with associated values

Intro

So far, we have learned about different types (Int, String, Array). 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.

Struct

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 and type. 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.

Activity: Coffee Struct

  1. Create a new playground in Xcode
  2. Create a struct named Coffee that has three properties:
    1. bean: a String that says what type of bean the coffee uses
    2. sugar: an Int that says how many sugar packets are in the coffee
    3. hasMilk: a Bool that says whether the coffee has milk or not

Creating an instance of a struct

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.

Activity: Coffee Instance

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.

Accessing Properties

We use dot syntax to access the properties in a struct.

let boba = BobaTea(tea: "black", sweetness: 25)
print(boba.tea) // black
print(boba.sweetness) //25

Dot syntax 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.

Challenge 1

Include the type BobaTea we just saw in your playground.

Write a structure called Order that will represent a client's order. It will need two properties:

  • name of type String
  • boba of type BobaTea 😯

Create-A-Boba

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
}

Activity: createCoffee

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.

Challenge 2

Let's dive a little deeper and build out a function for our order struct:

  1. Write a function that given input parameters returns an order (Type Order).
  2. Then create an order using the function.
  3. 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

Note your function should return an order. Then construct a print statement based on the order struct returned from createdOrder.

Methods

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.

Challenge 3

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.

Structures as value types

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
This shows that when boba is assigned to anotherBoba, it's copying the value into the new variable. They are two independent instances, with different memory addresses.

Exploring Swift types

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)

Questions

Q1: When is it useful to define a struct?

Q2: Which of the following is NOT a key component of a struct?

  1. Name
  2. Properties
  3. Functions
  4. Enumerations

Q3: What do you call a function that's added to a struct?

Classes

Classes are also named types with properties and methods.

We use classes to create objects.

class Customer {
  var firstName: String
  var lastName: String

  init(firstName: String, lastName: String) {
    self.firstName = firstName
    self.lastName = lastName
  }
}

let customer = Customer(firstName: "Monica", lastName: "Geller")
print(customer.firstName) // Monica
It's very similar to the way we write structs. Some differences: using the keyword class and the need for an initializer. Classes don't come with predefined initializers, and we must still assign an initial value to all the properties.

Accessing the value of properties is also using dot syntax.

This time we see the word self. In Swift, self is a property of an instance that holds the instance itself.

Activity - Cashier Class

Build a Cashier class that has firstName, lastName, and hasBathroomKey properties. Make sure you build out the init method for the class as well.

Challenge 4

Add the class Customer from earlier into your playground and refactor your code to use it.

Example: using a class to represent the customer, instead of just using a String for the name.

Classes as reference types

While structs are value types, classes are reference types.

This means than a variable of a class doesn't store an instance but a reference to a location in memory, and that location stores the instance.

[10m] BREAK

Enums

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

Common practice: start each case with lowercase.

Activity: Coffee Enum

Make a CoffeeType enum, where robusta, liberica, and arabica are valid types.

Switch statement

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.")
}

Activity: Coffee Case

Write a typeOfCoffee switch that prints out the name of the coffee type.

Raw values in enums

We can assign a value to each case in an enum. This can help 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
When we add raw values we need to specify their type right after the name of the enum. If we use Int, and don't specify otherwise, it will enumerate the cases starting with 0.

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.

Associated values in enums

  • 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

Let's see how that looks.

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.

Challenge 5

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.

For more practice with topics covered so far:

  1. Swift Playgrounds
  • You should be able to complete up to section 11

Questions

Q1: Which of the following would be best represented with an enumeration?

  1. Names of people in a room
  2. Political parties
  3. Addresses
  4. Compass degrees

Q2: Which of the following would be best represented with an enumeration? Choose all that apply

  1. Hair colors
  2. T-shirt sizes
  3. Favorite numbers
  4. Car speeds

Q3: Which of the following would be best represented with an enumeration? Choose all that apply

  1. Car manufacturers
  2. Wi-fi network names
  3. Professional basketball teams
  4. Shoe brands

After Class

  • Repl.it for Structs
  • Repl.it for Enums
  • Research what the operator === does
  • Research what you can achieve with switch statements regarding pattern matching and partial matching.
  • Check the documentation for Optionals and discover how they relate to today's lesson. Optional chaining

Additional Resources