What is the purpose of willSet and didSet in Swift?
Solution 1
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.
Solution 2
My understanding is that set and get are for computed properties (no backing from stored properties)
if you are coming from an Objective-C bare in mind that the naming conventions have changed. In Swift an iVar or instance variable is named stored property
Example 1 (read only property) - with warning:
var test : Int {
get {
return test
}
}
This will result in a warning because this results in a recursive function call (the getter calls itself).The warning in this case is "Attempting to modify 'test' within its own getter".
Example 2. Conditional read/write - with warning
var test : Int {
get {
return test
}
set (aNewValue) {
//I've contrived some condition on which this property can be set
//(prevents same value being set)
if (aNewValue != test) {
test = aNewValue
}
}
}
Similar problem - you cannot do this as it's recursively calling the setter. Also, note this code will not complain about no initialisers as there is no stored property to initialise.
Example 3. read/write computed property - with backing store
Here is a pattern that allows conditional setting of an actual stored property
//True model data
var _test : Int = 0
var test : Int {
get {
return _test
}
set (aNewValue) {
//I've contrived some condition on which this property can be set
if (aNewValue != test) {
_test = aNewValue
}
}
}
Note The actual data is called _test (although it could be any data or combination of data) Note also the need to provide an initial value (alternatively you need to use an init method) because _test is actually an instance variable
Example 4. Using will and did set
//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)")
}
}
var test : Int {
get {
return _test
}
set (aNewValue) {
//I've contrived some condition on which this property can be set
if (aNewValue != test) {
_test = aNewValue
}
}
}
Here we see willSet and didSet intercepting a change in an actual stored property. This is useful for sending notifications, synchronisation etc... (see example below)
Example 5. Concrete Example - ViewController Container
//Underlying instance variable (would ideally be private)
var _childVC : UIViewController? {
willSet {
//REMOVE OLD VC
println("Property will set")
if (_childVC != nil) {
_childVC!.willMoveToParentViewController(nil)
self.setOverrideTraitCollection(nil, forChildViewController: _childVC)
_childVC!.view.removeFromSuperview()
_childVC!.removeFromParentViewController()
}
if (newValue) {
self.addChildViewController(newValue)
}
}
//I can't see a way to 'stop' the value being set to the same controller - hence the computed property
didSet {
//ADD NEW VC
println("Property did set")
if (_childVC) {
// var views = NSDictionaryOfVariableBindings(self.view) .. NOT YET SUPPORTED (NSDictionary bridging not yet available)
//Add subviews + constraints
_childVC!.view.setTranslatesAutoresizingMaskIntoConstraints(false) //For now - until I add my own constraints
self.view.addSubview(_childVC!.view)
let views = ["view" : _childVC!.view] as NSMutableDictionary
let layoutOpts = NSLayoutFormatOptions(0)
let lc1 : AnyObject[] = NSLayoutConstraint.constraintsWithVisualFormat("|[view]|", options: layoutOpts, metrics: NSDictionary(), views: views)
let lc2 : AnyObject[] = NSLayoutConstraint.constraintsWithVisualFormat("V:|[view]|", options: layoutOpts, metrics: NSDictionary(), views: views)
self.view.addConstraints(lc1)
self.view.addConstraints(lc2)
//Forward messages to child
_childVC!.didMoveToParentViewController(self)
}
}
}
//Computed property - this is the property that must be used to prevent setting the same value twice
//unless there is another way of doing this?
var childVC : UIViewController? {
get {
return _childVC
}
set(suggestedVC) {
if (suggestedVC != _childVC) {
_childVC = suggestedVC
}
}
}
Note the use of BOTH computed and stored properties. I've used a computed property to prevent setting the same value twice (to avoid bad things happening!); I've used willSet and didSet to forward notifications to viewControllers (see UIViewController documentation and info on viewController containers)
I hope this helps, and please someone shout if I've made a mistake anywhere here!
Solution 3
You can also use the didSet
to set the variable to a different value. This does not cause the observer to be called again as stated in Properties guide. For example, it is useful when you want to limit the value as below:
let minValue = 1
var value = 1 {
didSet {
if value < minValue {
value = minValue
}
}
}
value = -10 // value is minValue now.
Solution 4
These are called Property Observers:
Property observers observe and respond to changes in a property’s value. Property observers are called every time a property’s value is set, even if the new value is the same as the property’s current value.
Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/ca/jEUH0.l
I suspect it's to allow for things we would traditionally do with KVO such as data binding with UI elements, or triggering side effects of changing a property, triggering a sync process, background processing, etc, etc.
Solution 5
NOTE
willSet
anddidSet
observers are not called when a property is set in an initializer before delegation takes place
Related videos on Youtube
Comments
-
zneak almost 4 years
Swift has a property declaration syntax very similar to C#'s:
var foo: Int { get { return getFoo() } set { setFoo(newValue) } }
However, it also has
willSet
anddidSet
actions. These are called before and after the setter is called, respectively. What is their purpose, considering that you could just have the same code inside the setter?-
mfaani about 7 yearsI personally don't like many answers here. They go too much down into the syntax. The differences are more about semantics and code readiblity. Computed Property (
get
&set
) are basically to have a property computed based on another property, e.g. converting a label'stext
into a yearInt
.didSet
&willSet
are there to say...hey this value was set, now let's do this e.g. Our dataSource was updated...so let's reload the tableView so it would include new rows. For another example see dfri's answer on how to call delegates indidSet
-
Mihir Oza almost 3 yearsEasiest answer found in the comment.
-
-
zneak about 10 yearsAre you saying that there is a performance advantage to using
willSet
anddidSet
versus equivalent setter code? This seems like a bold claim. -
eonil about 10 years@zneak I used wrong word. I am claiming programmer effort, not the processing cost.
-
zneak about 10 yearsThis is the Swift example.
getFoo
andsetFoo
are simple placeholders for whatever you'd like the getters and setters to do. C# doesn't need them either. (I did miss a few syntactical subtleties as I asked before I had access to the compiler.) -
Analog File about 10 yearsOh, ok. But the important point is that a computed property does NOT have an underlying storage. See also my other answer: stackoverflow.com/a/24052566/574590
-
Klaas almost 10 yearsAttention:
willSet
anddidSet
are not called when you set the property from within an init method as Apple notes:willSet and didSet observers are not called when a property is first initialized. They are only called when the property’s value is set outside of an initialization context.
-
pronebird over 9 yearsWhy cant' use I use didSet together with get and set..?
-
Andreas over 9 yearsBut they seem to be called on an array property when doing this:
myArrayProperty.removeAtIndex(myIndex)
...Not expected. -
evfemist over 9 years
//I can't see a way to 'stop' the value being set to the same controller - hence the computed property
warning dissapear after I usedif let newViewController = _childVC {
instead ofif (_childVC) {
-
user3675131 over 9 yearsget and set are used to create a computed property. These are purely methods, and there is no backing storage (instance variable). willSet and didSet are for observing changes to stored variable properties. Under the hood, these are backed by storage, but in Swift it's all melded into one.
-
Marmoy about 8 yearsYou can wrap the assignment in a defer { } statement within the initialiser which causes the willSet and didSet methods to be called when the initialiser scope is exited. I'm not necessarily recommending it, just saying that it's possible. One of the consequences is that it only works if you declare the property optional, since it is not strictly being initialised from the initialiser.
-
JW.ZG about 8 yearsIn your example 5, in
get
, I think you need to addif _childVC == nil { _childVC = something }
and thenreturn _childVC
. -
Vikash Rajput about 8 yearsPlease explain below line. I'm not getting, is this method or variable var propertyChangedListener : (Int, Int) -> Void = { println("The value of myProperty has changed from ($0) to ($1)") }
-
ramazan polat almost 8 yearsInitialising properties in same line is NOT supported in Swift 3. You should change the answer to conform swift 3.
-
zneak almost 8 years@RamazanPOLAT, with that said, this still appears to work on Swift 3. (Press the play button at the bottom to execute it.)
-
ramazan polat almost 8 years@zneak my bad. Looks like xcode playground messes things up when you have lots of codes in one playground page.
-
eemrah about 7 yearsor using willSet make sense some effects on this outlets methods , isn't it ?
-
Aaron about 6 yearsTo make an observer trigger in an init, create a setValue function that makes the assignment to the observed variable, and call that setValue function from the init function, it will execute the observers.