Property Observers Willset and Didset; Property Getters and Setters

Property observers willSet and didSet; Property getters and setters

When and why should I use willSet/didSet

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

Consider your example with outputs:


var variable1 : Int = 0 {
didSet{
print("didSet called")
}
willSet(newValue){
print("willSet called")
}
}

print("we are going to add 3")

variable1 = 3

print("we added 3")

Output:

we are going to add 3
willSet called
didSet called
we added 3

it works like pre/post -condition

On the other hand, you can use get if you want to add, for example, a read-only property:

var value : Int {
get {
return 34
}
}

print(value)

value = 2 // error: cannot assign to a get-only property 'value'

Getters and Setters in Swift - Does it make sense to use WillSet and DidSet instead?

Your question boils down to compile time vs. run time error. To address your 3 questions:

  1. Yes, willCheck is your only option here
  2. Readonly properties fall into 2 types: (a) those whose value derive from other properties, for example, their sum; and (b) those that you want to be able to change by yourself, but not by the users. The first type truly have no setter; the second type has a public getter and a private setter. The compiler can help you check for that and the program will not compile. If you throw a fatalError in didSet you get a runtime error and your application will crash.
  3. There can be state objects that you don't want the user to freely mess with, and yes, you can completely hide those from the users.

Your code first example was too verbose in defining the backing variables - you don't need to do that. To illustrate these points:

class Test
{
// 1. Validate the new value
var mustBeginWithA: String = "A word" {
willSet {
if !newValue.hasPrefix("A") {
fatalError("This property must begin with the letter A")
}
}
}

// 2. A readonly property
var x: Int = 1
var y: Int = 2
var total: Int {
get { return x + y }
}

private(set) var greeting: String = "Hello world"
func changeGreeting() {
self.greeting = "Goodbye world" // Even for private property, you may still
// want to set it, just not allowing the user
// to do so
}

// 3. Hide implementation detail
private var person = ["firstName": "", "lastName": ""]
var firstName: String {
get { return person["firstName"]! }
set { person["firstName"] = newValue }
}

var lastName: String {
get { return person["lastName"]! }
set { person["lastName"] = newValue }
}

var fullName: String {
get { return self.firstName + " " + self.lastName }
set {
let components = newValue.componentsSeparatedByString(" ")
self.firstName = components[0]
self.lastName = components[1]
}
}
}

Usage:

let t = Test()
t.mustBeginWithA = "Bee" // runtime error

t.total = 30 // Won't compile

t.greeting = "Goodbye world" // Won't compile. The compiler does the check for you
// instead of a crash at run time

t.changeGreeting() // OK, greeting now changed to "Goodbye world"

t.firstName = "John" // Users have no idea that they are actually changing
t.lastName = "Smith" // a key in the dictionary and there's no way for them
// to access that dictionary

t.fullName = "Bart Simpsons" // You do not want the user to change the full name
// without making a corresponding change in the
// firstName and lastName. With a custome setter, you
// can update both firstName and lastName to maintain
// consistency

A note about private in Swift 2 vs. Swift 3: if you try this in a Swift 2 playground, you will find t.greeting = "Goodbye world" works just fine. This is because Swift 2 has a strange access level specifier: private means "only accessible within the current file". Separate the class definition and the sample code into different files and Xcode will complain. In Swift 3, that was changed to fileprivate which is both clearer and save the private keyword for something more similar to to Java and .NET

What is the purpose of willSet and didSet in Swift?

The point seems to be that sometimes, you need a property that has automatic storage and some behavior, for instance to notify other objects that the property just changed. When all you have is get/set, you need another field to hold the value. With willSet and didSet, you can take action when the value is modified without needing another field. For instance, in that example:

class Foo {
var myProperty: Int = 0 {
didSet {
print("The value of myProperty changed from \(oldValue) to \(myProperty)")
}
}
}

myProperty prints its old and new value every time it is modified. With just getters and setters, I would need this instead:

class Foo {
var myPropertyValue: Int = 0
var myProperty: Int {
get { return myPropertyValue }
set {
print("The value of myProperty changed from \(myPropertyValue) to \(newValue)")
myPropertyValue = newValue
}
}
}

So willSet and didSet represent an economy of a couple of lines, and less noise in the field list.

SwiftUI View Property willSet & didSet property observers not working

EDIT:
On iOS 14 property observers work the same as they did in iOS 13. But, we now have the .onChange(of:perform:) as a replacement. Docs

Text(self.myString).onChange(of: self.myString) { newValue in print("myString changed to: \(newValue)") }

Property observers on basic vars technically work in SwiftUI. If you do something like var view = MyTestView(...) and then view.FSAC = "updated" the the observers will fire (I've verified this).

However, typically with SwiftUI you construct the View (which is a struct not a class) within body during each layout pass. In your case var body: some View { MyTestView(FSAC: "FSAC Value") }.

Property observers do not fire during init, and therefore they aren't usually useful in SwiftUI Views.

If you would like to update some sort of State during init, take a look at this answer.

A property in a struct not being updated after being computed in willSet observer

Property observers are not called on initialisation, only when the value is set after that, that's why didSet is not being executed.

In this specific case, since isMillionaire is completely derived from balance and shouldn't be able to be updated directly, I would recommend using a computed property, so it would look like this:

var isMillionaire: Bool {
return balance > 1_000_000
}

Swift: use property observers while defining variables one per line

[Note: There's no such thing as "property observer". Swift variables — not just properties, but any variable — can have setter observers (willSet and didSet). You can't observe the getting of a stored variable. Also, you can't have a setter observer on a let variable, because (wait for it) it can't be set. My answer is thus based on mentally changing your question to fit those facts. Even so, I don't really see what the question is, because I do not see in what sense setter observers "disperse variables across the class". However...]

A variable's setter observer is part of the variable declaration. It can't be appended later. The closest you can come to appending a setter observer separately from the variable declaration is to subclass (though this, it seem to me, would be a very silly and confusing thing to do gratuitously):

class User {
var (var1, var2, var3) = (0,0,0) // super neatness
}
class MyUser:User { // add setter observers
override var var1 : Int {
didSet {}
willSet {}
}
override var var2 : Int {
didSet {}
willSet {}
}
override var var3 : Int {
didSet {}
willSet {}
}
}

Changing Values with WillSet Property Observer

it is not possible to change the values within the willSet observer. overriding the getter and setter could work:

override var frame: CGRect {
get { return super.frame }
set {
var tempFrame = newValue
tempFrame.origin.y = min(max(tempFrame.origin.y, minimumOriginY), maximumOriginY)
super.frame = tempFrame
}
}


Related Topics



Leave a reply



Submit