Why no Infinite loop in didSet?
If you check Apple's documentation for Swift in The Swift Programming Language - Properties, Apple says that:
Note:
If you assign a value to a property within its own didSet observer, the new value that you assign will replace the one that was just set.
So if you put a breakpoint in the first line of your didSet
block, I believe it should only be called once.
Loops in didSet
If you use defer, for updating any optional properties or further updating non-optional properties that you've already initialized and after you've called any super init methods, then your willSet, didSet, etc. will be called.
for item in subItems {
defer{
item.depthA = depthA + 1
}
}
When you use the forEach it makes kindOf "contract" with the elements and because it's an instance method unlike for .. in loop, it triggers the didSet of the variable. The above case applies where we use the loop, we have to trigger the didSet manually
This Solves the problem I think. Hope it helps!!
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
}
}
}
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.
How to avoid infinite loop on swift property observer
You're not going to be able to do this with a property. You need a method with a parameter indicating which parent/child layer not to update. So when a parent updates its childrens' frames it would pass in a reference to itself so the children don't update the parent frame. And when a child updates its parent's frame it would pass in a reference to itself so the parent doesn't update that child.
class Layer {
var parent: Layer?
var children: [Layer]
private(set) var frame: CGRect
func update(frame: CGRect) {
update(frame: frame, sender: nil)
}
private func update(frame: CGRect, sender: Layer? = nil) {
self.frame = frame
if sender !== parent {
parent?.update(frame: SOME_FRAME, sender: self)
}
children.forEach { child in
if child !== sender {
child.update(frame: SOME_FRAME, sender: self)
}
}
}
}
Why lazy property and didSet will end up with recursion call?
You are declaring a lazy stored
property. When the struct is initialized with MyStruct()
, there's no value stored in MyStruct.x
.
It will only be populated when it is accessed first time. When a property is changed, a value type like struct MyStruct
is considered to be changed as well - so it's didSet
is invoked (again) upon first access of x
.
Here's how it becomes infinite loop.
viewDidLoad() > A.s.setter > A.s.didset
[Expected]- First access of
A.n.getter
for theprint(n)
part. s.x
is lazy stored and upon first value population (update), it triggers -A.s.modify > A.s.didset
& we land again atA.n.getter
.- It loops indefinitely between
2
&3
after this.
See screenshot -
How can I set a string variable and make it always lowercase?
I stand corrected, this is the correct approach. LeoDabus deserves the credit for this answer:
var alwaysLowercaseString : String? {
didSet{
alwaysLowercaseString = alwaysLowercaseString?.lowercaseString
print(alwaysLowercaseString)
}
}
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.
Related Topics
How to Get All Days in Current Week in Swift
Scenekit - Scntext Centering Incorrectly
How to Append a Character to a String in Swift
How to Reload a UI View's Content Swift
Spritekit Not Deallocating All Used Memory
With Firebase, Swift Removeobserver(Withhandle Does Not Remove the Observer
Watchos3 Complication That Launches App
Is It Possible in Swift to Add Variables to an Object at Runtime
Convert Timestamp String with Epochal Time and Timezone into Nsdate
Swift Programming Nserrorpointer Error etc
Swift Error: Guard Body Must Not Fall Through
Swift Property Observer in Protocol Extension
Failed to Get Descriptors for Extensionbundleid
Changing Value in Nested Dictionary in Swift
Calculate Area of Mkpolygon in an Mkmapview
Use 'Self' as a Default Parameter
Which Swift Character Count Should I Use When Interacting with Nsstring APIs