*EXTRA ENTRY* Properties and Initializers - recap!

Technically, I promised not so many entries but I really feel the need to go over some of this! There was a lot of content in the last couple of sessions that I did not feel were clearly explained enough - no offence Pasan but they were hard to follow! So now is the time to seek out a bit more information. This is very 'casual' - I have an Avengers film on in the background and am not going under any time constraints or anything - I have plenty this evening. So I just want to clarify a few things and get some hands-on practice. Let's go!

Properties

From Pasan's explanations, there are several different types of these. This is what I know:

Stored properties - these are values which are either declared within the class/struct/enum, or declared upon initialization. 

Computed properties - these require a calculation and a reference to one of the stored properties, which may not yet have a value. The computed property can be accessed when an instance is created. They are READ ONLY. 

Type properties - these use the 'static' keyword and do not actually hold any memory. They keep this way until actually accessed. A value is given to them as they are accessed rather than set with an instance. 


OK, so all of that makes sense in my head!


Get/Set

This is where I need some more practice with the syntax etc. These are used with computed properties. Let me practice on a playground first before I check anything from my notes....

struct Minotaur {
    
    var attack: Int
    var defense: Int
    var speed = 3
    
    get {
    var attackChance: Int = attack * speed
    }
    set {
    attackChance = 18
    }
}

All of the top of my head (Heroes of Might and Magic 2 as an inspiration!) but it's not working. Let's check my blog on this....

OK, here's one way to do computed properties without get/set:

struct Rectangle {
    
    let length: Int
    let width: Int
    
    var area: Int {
        return length * width
        
    }
}

Start of another one...

struct Rectangle2 {
    var origin = Point()
    var size = Size()
    
    var centre: Point {
        get {

OK, didn't get the syntax.....

struct Minotaur {
    
    var attack: Int
    var defense: Int
    var speed = 3
    
    var attackChance: Int {
    get {
    return attack * speed
    }
    set {
    return attackChance = 18
    }
}

}

Yes! With a bit of syntax that now works! So the whole get/set thing makes sense at the moment. Let's how it works with instances of Minotaur....

let creature1 = Minotaur(attack: 4, defense: 2, speed: 3)

creature1.attackChance

Playground still running so I can't see if the result will be 12 or 18...Another question is when to use get/set?

Let's look up some documentation....

OK nothing is clarified in the Swift eBook! Shame on you!

Now having a look on 'Stack Overflow'....


Right here's an answer that helps clarify things:


You should be referring to Read-Only Computed Properties instead of Computed Properties.
A computed property with a getter but no setter is known as a read-only computed property.It always returns a value, and cannot be set to a different value.
Computed Properties do not actually store a value. Instead, they provide a getter and an optional setter to retrieve and set other properties and values indirectly.
Computed properties, including read-only properties must be set as a variable and not a constant.
struct Square {
    var edge: Double = 0
    
    var area: Double {
        get {
            return edge * edge
        }
        set (newArea){
            edge = sqrt(newArea)
        }
    }
}

In this case, setter can't be used to set the value of the property but to set the values of other properties, from which the computed property is computed.
About newValue, it's called Shorthand Setter Declaration - If a computed property’s setter does not define a name for the new value to be set, a default name of newValue is used.
So, the above example becomes:

struct Square {
    var edge: Double = 0
    
    var area: Double {
        get {
            return edge * edge
        }
        set {
            edge = sqrt(newValue) //someSquare.area = 25 (25 is newValue)
        }
    }
}

Right let's make sense of this all....

The Square struct has a stored property of edge and a computed property of area. 

Area can be 'gotten' - this is the read-only part that can be accessed on an instance. Right, got that.

Setter is optional. Here is the part that clarifies it finally - 

In this case, setter can't be used to set the value of the property but to set the values of other properties, from which the computed property is computed.

So in other words, the setter can change the value of OTHER properties - in this case the stored property of edge! newValue is the designated keyword for the new value of the property that has been set. 

OK, that makes more sense! Thanks Stack Overflow, specifically Ajith R Naynak! 


OK moving on....

willSet/didSet

Still unsure about this - let's look that up on Stack Overflow first....

OK, first of all these are called property observers. Let's look for that. 

Here looks like a useful website:


Right, some direct quotes first:

Property observers are Swift's way of letting you attach functionality to changes in property values. For example, you might want to say, "whenever the player's score changes, update this label to show their new score." Here's a basic example that prints message to the debug console when a variable changes:

var score = 0 {
    willSet {
        print("Score is about to change to \(newValue)")
    }
    
    didSet {
        print("Score just changed from \(oldValue) to \(score)")
    }
}

score = 10

Ok, so according to this guy, it's about changes in property values....printing something to the console to show before (willSet) and after (didSet) the property value has changed. Not sure when the willSet part would actually be executed first!

Another example from a guy called Bob:




var myGrade: Int = 80 {
    willSet(newGrade) {
        print("About to change your grade to \(newGrade)")
    }
    didSet {
        print("Your grade has been changed")
        print("you had \(oldValue) previously. Now you have \(myGrade)")
    }
}

Let's break this down.... 

The willSet uses a new temporary local constant called 'newGrade' and in the compiler, the message of 'About to change...' will come up when newGrade is BEING CHANGED. 

The didSet will execute when the value has actually been changed. oldValue is a default value that Swift uses that you can access. Cool!


myGrade = 100
// "About to change your grade to 100"
// "Your grade has been changed"
// "You had 80 previously. Now you have 100"

So the first line will come up while myGrade is being accessed. OK. 

Create a variable called, totalSteps. When the variable encounters a new value, you may notify the user that the value has been changed.

var totalSteps: Int = 20 {
    willSet(newTotalSteps) {
        print("About to set totalSteps to \(newTotalSteps)")
    }
    didSet {
        if totalSteps > oldValue  {
            print("Added \(totalSteps - oldValue) steps")
        }
    }
}

Yup that definitely makes sense now. 

The difference between property observers and computed properties are:

  1. There is a default value and it is observed rather than calculated.
  2. willSet and didSet will not get called when you initialize it
Thanks Bob! I'm sticking with his website as he goes onto explain failable inits - my next thing to tackle...

Failable Initializers

Ok some snippets of code to copy first:

enum NameError: Error {
    case noName
}

First of all, this is using the 'Error' type that I had not actually seen up until today. This just creates the 'noName' error, which we're about to use....

struct UdemyCourse {
    let courseName: String
    
    init(name: String) throws {
        if name.isEmpty {
            throw NameError.noName
        }
        self.courseName = name
    }
}


do {
    let myCourse = try UdemyCourse(name: "Bob")
    myCourse.courseName
    
} catch NameError.noName {
    print("Bob, please enter the name")
}

What's going on here...damn Error Handling!! I have here the 'throws' keyword and the .isEmpty method. That is syntax I'm not very good with but it does make sense. 

The do/catch part is trying out to see if the error is returned or not. 

class Blog {
    let name: String
    init?(name: String) {
        if name.isEmpty {
            // handle error
            return nil
        }
        self.name = name
    }
}

let blog = Blog(name: "") // nil

if let myBlog = blog {
    print(myBlog.name)
}

So here is the failable init example above. The init with a ? is the key part here. Still not 100% but Bob has helped to make sense with it. 

I like his website and notes. Plus he has videos for each one. After Treehouse, I may be using his website to go over things!

Initializer Delegation

Last bit for now! Looking at several websites, I'm struggling to find anything concrete and/or worth copying over.

What I have gleaned is that this gives multiple ways of initializing an object/instance. So, let's say that you had some default values you may only have some of them needing init, if one or more were set. 

Everything I read seems to mention 'Convenience' inits, which is where init happens in another way. 


Convenience initializers, however, rely on a designated initializer to create a fully initialized instance of the class. That's why the init() initializer of the Task class invokes the init(name:) initializer in its implementation. This is referred to as initializer delegation. The init() initializer delegates initialization to a designated initializer to create a fully initialized instance of the Task class.


Confusing! Either Pasan is wrong, or Swift has been updated since his course, or that it's just not the same thing. 

OK, so initializer delegation is still a bit of a puzzle. The best way to look at that one is just simply that init delegation means alternative/multiple ways of initalizing.  It gives many options to create particular instances, rather than just creating more structs/classes/enums!

Let's have a go...

Had a go with the Minotaur struct and I can't make sense of it!

Well, I'll have to leave it for now and hope I can get it with more practice with convenience init etc. 

OKayyyy, something that works:

struct Food {
    
   var name: String
   var calories: Int
    
    init(name: String, calories: Int) {
        self.name = name
        self.calories = calories
    }
    
     init(calories: Int) {
        self.init(name: "Unknown", calories: calories)
    
}


}

In this one, non convenience used - so structs don't need this keyword! Could also have done this:

struct Food {
    
   var name: String
   var calories: Int
    
    init(name: String, calories: Int) {
        self.name = name
        self.calories = calories
    }
    
     init(calories: Int) {
        self.name = "Unknown"
        self.calories = calories
    
}


}


Interestingly, to do this as a class, you have to use the first one - the self.init syntax:

class Food {
    
   var name: String
   var calories: Int
    
    init(name: String, calories: Int) {
        self.name = name
        self.calories = calories
    }
    
    convenience init(calories: Int) {
        self.init(name: "Unknown", calories: calories)
    
}


}

And of course use initializer! 

So some REALLY good practice and I am glad that I persevered! Would have been easy just to have cracked on. It means I can approach the next bit doing all that I've can to understand properties and initialization better - which I definitely do! 

Comments

Popular posts from this blog

*Xcode Project Entry 2* F1 Quiz - part 1

Angela Yu Course Part 10 (up to lesson 112)

Angela Yu Xcode 12 Course - Part 7 (lectures 74 to 79)