Structs

Structs are one of the way Swift lets us create our “own data types” out of several small types. We can create our “own variables and own functions” in Structs. Every piece of UI we design is built on a struct, with lots of structs inside.

Let’s create “a new type” called Album, with 2 string constants and an interger. we also add a simplefunction that summarizes the values of our three constants. Notice how ‘Album’ starts with “a capital A”. When we are referring to a data type, we use upper case where the first letter is. Remember, for the most part this is only “a convention” rather than a rule, but it’s a helpful one to stick with.

struct Album {
    let title: String
    let artist: String
    let year: Int
    
    func printSummary() {
        print("\(title) (\(year)) by \(artist)")
    }
}

Now, we could make a couple of albums, then print some of their values and call their functions. Notice how we can create a new Album as if we were calling a function - we Have to Provide values for each of the constants in the Order they were defined.

let slideshows = Album(title: "20 Good Reasons", artist: "Thirsty Merc", year: 2007)
let evolve = Album(title: "Walking the Wire", artist: "Imagine Dragons", year: 2017)

print(slideshows)
print(evolve.artist)

slideshows.printSummary()
evolve.printSummary()

Where things get more interesting is when you want to have “value that can change”. Let’s create an Employee struct that can take vacation as needed.

struct Employee {
    let name: String
    var vacationRemaining: Int
    
    mutating func takeVacation(days: Int) {
        if vacationRemaining > days {
            vacationRemaining -= days
            print("I'm going on vacation!")
            print("Days remaining: \(vacationRemaining)")
        } else {
            print("Oops! There aren't enough days remaining")
        }
    }
}

var Kido = Employee(name: "Taiki Kido", vacationRemaining: 14)
Kido.takeVacation(days: 7)
print(Kido.vacationRemaining)


Names used in Structs

Properties = Variables and constants that belong to structs Methods = Functions that belong to structs Instance = a Variable or Constant we create out of a struct. for example, we created instances of Album like this:

let slideshows = Album(title: "20 Good Reasons", artist: "Thirsty Merc", year: 2007)
let evolve = Album(title: "Walking the Wire", artist: "Imagine Dragons", year: 2017)

How to Compute property Values Dynamically

Structs can have two kinds of property.

  1. a Stored property is a variable or constant that holds a piece of data inside an instance of the struct.
  2. a Computed property calculates the value of the property dynamically every time it’s accessed.

We can make the Emplyee stuct simple here.

struct Employee1 {
    let name: String
    var vacationRemaining: Int
}

var park = Employee1(name: "HyoShin Park", vacationRemaining: 14)
park.vacationRemaining -= 1
print(park.vacationRemaining)
park.vacationRemaining -= 5
print(park.vacationRemaining)

Now, we can adjust this to use computed property.

struct Employee2 {
    let name: String
    var vacationAllocated = 14
    var vacationTaken = 0
    
    var vacationRemaining: Int {
        vacationAllocated - vacationTaken
    }
}

var yoon = Employee2(name: "JongShin Yoon", vacationAllocated: 14)
yoon.vacationTaken += 4
print(yoon.vacationRemaining)
yoon.vacationTaken += 3
print(yoon.vacationRemaining)


didSet / willSet

Swift lets us create property observers, which are special pieces of code that run when properties change. didSet observer is to run when the property JUST Changed. didSet is called immediately after the new value is stored. willSet observer is to run just BEFORE the property changed. willSet is called just before the value is stored.

struct Game {
    var score = 0
}

var game = Game()
game.score += 10
print("Score is now \(game.score)")
game.score -= 2
print("Score is now \(game.score)")
game.score += 5
print("Score is now \(game.score)")

Now, This is very ugly. Every time the score changes, we have to write the ‘print()’ line following.

So, to solve this problem, We can attach the ‘print()’ call directly to the property using ‘didSet’. Whenever the properties change, it always runs the code inside. Like this:

struct Game2 {
    var score = 0 {
        didSet {
            print("Score for Game 2 is now \(score)")
        }
    }
}

var game2 = Game2()
game2.score += 3
game2.score -= 3
game2.score += 2

We can demonstrate all this functionality in action using one code sample, which will print messages as the values change so you can see the flow when the code is run:

struct App {
    var contacts = [String]() {
        willSet {
            print("willSet - Current value is: \(contacts)")
            print("willSet - There are now \(contacts.count) contacts.")
            print("willSet - New value will be: \(newValue)")
        }

        didSet {
            print("didSet - Current value is: \(contacts)")
            print("didSet - There are now \(contacts.count) contacts.")
            print("didSet - Old value was \(oldValue)")
        }
    }
}

var app = App()
app.contacts.append("Jamie Miller")
app.contacts.append("HyoShin Park")
app.contacts.append("SiKyung Sung")

willSet is called just before the value is stored. didSet is called immediately after the new value is stored.


Initializers

Initializers are called to create a new instance of a particular type. In its simplest form, an initializer is like an instance method with no parameters, written using the init keyword.

struct Player {
    let name: String
    let number: Int

    init(name: String, number: Int) {
        self.name = name
        self.number = number
    }
}

let player1 = Player(name: "Oasis", number: 7)
print(player1.name)
print(player1.number)

By writing self.name we’re clarifying we mean “the name property that belongs to my current instance.

Now, if we want to provide a player name, but theshirt number is randomized, we can write code like this.

struct Player2 {
    let name: String
    let number: Int
    
    init(name: String) {
        self.name = name
        number = Int.random(in: 1...20)
    }
}

let player2 = Player2(name: "Son")
print(player2.number)

Important!: All properties must have a value by the time the initializer ends. If we had not provided a value for number inside the initializer, Swift would refuse to build our code.