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:
- Yes,
willCheck
is your only option here - 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
indidSet
you get a runtime error and your application will crash. - 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 var
s 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 View
s.
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
Type Conversion When Using Protocol in Swift
Make Code With Firebase Asynchronous
Finish Asynchronous Task in Firebase With Swift
Swift: How to Detect Linear Type Barcodes
Swap Rootviewcontroller With Animation
Swift Xcode Index Freezing or Slow
How to Document the Parameters of a Function'S Closure Parameter in Swift 3
How to Access Program Arguments in Swift
Get Button Pressed Id on Swift Via Sender
Programmatically Screenshot | Swift 3, Macos
Is Swiftui Backwards-Compatible With iOS 12.X and Older
Swiftui: Pop to Root View When Selected Tab Is Tapped Again
Whats the Swift Animate Withduration Syntax
Draw Scenekit Object Between Two Points
What Are "Intervals" in Swift Ranges
Rounding in Swift with Round()
What Is the Use of "Static" Keyword If "Let" Keyword Used to Define Constants/Immutables in Swift