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.
Variable with getter/setter cannot have initial value, on overridden stored property
In swift you are able to override properties only with computed properties (which are not able to have default values) with same type. In your case, if you wish override test
property in SecondViewController
you need write something like this:
override var test: Float {
get {
return super.test
}
set {
super.test = newValue
}
}
And there is no way to override didSet
/willSet
observers directly; you may do this by write other methods invoked in observers and just override them:
FirstViewController:
internal var test: Float = 32.0 {
willSet {
test_WillSet(newValue)
}
didSet {
test_DidSet(oldValue)
}
}
func test_WillSet(newValue: Float) {}
func test_DidSet(oldValue: Float) {}
SecondViewController:
override func test_WillSet(newValue: Float) {
super.test_WillSet(newValue)
}
override func test_DidSet(oldValue: Float) {
super.test_DidSet(oldValue)
}
Can stored property in Swift have getter and setter?
You have willSet and didSet.
That should be enough of an answer, but stackoverflow thinks it is too short :-)
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
}
}
Can I use willSet and/or didSet to trigger a transition to a new view controller based on the value of a variable?
This the method is inside the class than you should be able to do!. Check this answer!
//True model data
var _test : Int = 0 {
//First this
willSet {
println("Old value is \(_test), new value is \(newValue)")
}
//value is set
//Finaly this
didSet {
println("Old value is \(oldValue), new value is \(_test)")
}
}
Does setting the same value for a property still call willSet and didSet?
Yes, willSet and didSet get called even when setting to the same value. I tested it in a playground:
class Class1 {
var willSetCount = 0
var didSetCount = 0
var var1: String = "A" {
willSet {
willSetCount++
}
didSet {
didSetCount++
}
}
}
let aClass1 = Class1() // {0 0 "A"}
aClass1.var1 = "A" // {1 1 "A"}
aClass1.var1 = "A" // {2 2 "A"}
If you want to prevent side effects from happening when the same value is being set, you can compare the value to newValue/oldValue:
class Class2 {
var willSetCount = 0
var didSetCount = 0
var var1: String = "A" {
willSet {
if newValue != var1 {
willSetCount++
}
}
didSet {
if oldValue != var1 {
didSetCount++
}
}
}
}
let aClass2 = Class2() // {0 0 "A"}
aClass2.var1 = "A" // {0 0 "A"}
aClass2.var1 = "B" // {1 1 "B"}
Related Topics
Swift Firebase Using an Unspecified Index
How to Conform an Observableobject to the Codable Protocols
How to Convert 4 Bytes to a Swift Float
Swift Firestore Check If Documents Exists
Implemented Helper App But Does Not Launch on Login
Alamofire Returns Wrong Encoding
Why Are Uiscreen.Bounds Incorrect in iOS11
Swift: Convert String to Hex Color Code
How to Prevent Multiple Instances of the Same Window from Opening in MACos
Custom Uitabbar Unselected Item's Color
Create a Loading Image/ Activity Indicator, Until the Image Is Shown in the Screen in Swift
Set Uitextfield Placeholder Color Programmatically
Ambigious Reference to Member Request() Issues with Alamofire After Migration to Swift 3
Delegate Property with Different Type in Swift
Avplayer Seektotime Not Working Properly
Swift Compilation Time with Nil Coalescing Operator
How to Get Walking and Running Distance Using Healthkit in Swift