In Swift, does resetting the property inside didSet trigger another didSet?
I also thought, that this is not possible (maybe it wasn't in Swift 2), but I tested it and found an example where Apple uses this. (At "Querying and Setting Type Properties")
struct AudioChannel {
static let thresholdLevel = 10
static var maxInputLevelForAllChannels = 0
var currentLevel: Int = 0 {
didSet {
if currentLevel > AudioChannel.thresholdLevel {
// cap the new audio level to the threshold level
currentLevel = AudioChannel.thresholdLevel
}
if currentLevel > AudioChannel.maxInputLevelForAllChannels {
// store this as the new overall maximum input level
AudioChannel.maxInputLevelForAllChannels = currentLevel
}
}
}
}
And below this piece of code, there is the following note:
In the first of these two checks, the didSet observer sets currentLevel to a different value. This does not, however, cause the observer to be called again.
How is didSet called again when setting inside through a function?
After checking with Swift github and asking questions about this problem, I find out that this problem is more complex as it seems. But there is a specific rule about this problem:
didSet
observer will not trigger only if access to property within
its owndidSet
observer can be done through direct memory access.
Problem is that it is a little ambiguous when access to property will be direct(unless probably if you are developer of Swift). An important feature that has an effect on my question is this:
Class instance method never access class properties directly.
This quote shows problem with my code, even though I can argue that when an instance member should be able to access property directly whenever you call it in didSet
observe. When I have a code like this:
class B {
var i = 0 {
didSet {
print("called")
doit()
}
}
func doit() {
self.i += 1
}
}
doit()
function cannot access i
directly which triggers didSet
again causing infinite loop.
Now what is the workaround?
You can use inout
for passing properties from its own didSet
to a instance function without triggering didSet
. Something like this:
class B {
var i = 0 {
didSet {
print("called")
doit(&i)
}
}
func doit(_ i: inout Int) {
i += 1
}
}
And one last thing. Starting Swift 5, conditions for selecting direct memory access for properties within its own didSet
will become more restricted. Based on github, only conditions that will use direct memory access is are the following:
Within a variable's own didSet/willSet specifier, access its storage
directly if either:
1) It's a 'plain variable' (i.e a variable that's not a member).
2) It's an access to the member on the implicit 'self' declaration.
If it's a member access on some other base, we want to call the setter
as we might be accessing the member on a *different* instance.
This means codes like following will trigger infinite loop while it does not right now:
class B {
var i = 0 {
didSet {
print("called")
var s = self
s.i += 1
}
}
}
Is there a way to get didSet to work when changing a property in a class?
Nothing happens because the observer is on test
, which is a Foo instance. But you changed test.number
, not test
itself. Foo is a class, and a class is a reference type, so its instances are mutable in place.
If you want to see the log message, set test
itself to a different value (e.g. a different Foo()
).
Or, add the println
statement to the other didSet
, the one you've already got on Foo's number
property.
Or, make Foo a struct instead of a class; changing a struct property does replace the struct, because a struct is a value type, not a reference type.
Inner didSet protection bizarrely extends to the whole class?
This is bug SR-419.
From the comment on the bug:
Ugh. We really need to check that the base of the property access is statically
self
.
and from my experiments it seems that the didSet
observer is not invoked only if you set the same property on any object. If you set any other property (even on the same object), the observer is invoked correctly.
class A {
var name: String
var related: A?
var property1: Int = 0 {
didSet {
print("\(name), setting property 1: \(property1)")
self.property2 = 100 * property1
related?.property1 = 10 * property1
related?.property2 = 100 * property1
}
}
var property2: Int = 0 {
didSet {
print("\(name), setting property 2: \(property2)")
}
}
init(name: String) {
self.name = name
}
}
let a = A(name: "Base")
a.related = A(name: "Related")
a.property1 = 2
Output:
Base, setting property 1: 2
Base, setting property 2: 200
Related, setting property 2: 200
when the expected output should be:
Base, setting property 1: 2
Base, setting property 2: 200
Related, setting property 1: 20
Related, setting property 2: 2000
Related, setting property 2: 200
It seems you also need to assign that property directly from the observer. Once you enter another function (or observer), the observers start working again:
var property1: Int = 0 {
didSet {
print("\(name), setting property 1: \(property1)")
onSet()
}
}
...
func onSet() {
self.property2 = 100 * property1
related?.property1 = 10 * property1
related?.property2 = 100 * property1
}
And that is the best workaround.
Another workaround (thanks @Hamish) is to wrap nested assignments into an immediately executed closure:
var property1: Int = 0 {
didSet {
{
self.property2 = 100 * property1
related?.property1 = 10 * property1
related?.property2 = 100 * property1
}()
}
}
Depending on code before the closure, you might have to wrap it into parenthesis or insert a semicolon after the preceding statement.
Swift didSet does not fire
Why is it so?
didSet
fires when you change value of the property, in example it would be when array itself changes.
Here
exerciseSets[index].isEnabled = !exerciseSets[index].isEnabled
you change property of some object in the array, but array stays the same, and contains same objects. So didSet
won't be called.
Can I somehow use the former option?
No, you can't. Use latter.
EDIT: I checked @martin-r advice to use structs for ExerciseSet
, and it works, didSet
set is called. Because when property value change for structure basically means creation of new structure with all but changed field being copied.
Is it possible to allow didSet to be called during initialization in Swift?
Create an own set-Method and use it within your init-Method:
class SomeClass {
var someProperty: AnyObject! {
didSet {
//do some Stuff
}
}
init(someProperty: AnyObject) {
setSomeProperty(someProperty)
}
func setSomeProperty(newValue:AnyObject) {
self.someProperty = newValue
}
}
By declaring
someProperty
as type:AnyObject!
(an implicitly
unwrapped optional), you allow self to fully initialize without
someProperty
being set. When you call
setSomeProperty(someProperty)
you're calling an equivalent of
self.setSomeProperty(someProperty)
. Normally you wouldn't be able to
do this because self hasn't been fully initialized. Since
someProperty
doesn't require initialization and you are calling a
method dependent on self, Swift leaves the initialization context and
didSet will run.
Related Topics
Saving Array to Realm in Swift
What's the Difference Between a View and a Viewcontroller
Generate an Integer Binary Representation Using Swift
Treat *Some* Warnings as Errors in Swift
Swift Bindings Won't Work Xcode 6 Beta 5
Why Can't I Change Variables in a Protocol Extension Where Self Is a Class
How to Detect Switch Between MACos Default & Dark Mode Using Swift 3
How to Update Swift from 3.1 to 3.2, But Not to 4.0
Why Does Swift Provide Both a Cgrect Initializer and a Cgrectmake Function
How to Open Safari Extension Toolbaritem Popover Programmatically
Cast to a Metatype Type in Swift
Why Do I Still Need to Unwrap Swift Dictionary Value
How to Integrate Latest Sdwebimage API in My Swift Based Project
How Does Anyobject Conform to Nsobjectprotocol
Difference Between Println and Print in Swift
At Runtime, How Does Swift Know Which Implementation to Use
Why Do I Need to Declare an Optional Value as Nil Explicitly in Struct - Swift