21

I'm testing this and it appears that if you change the value within didSet, you do not get another call to didSet.

var x: Int = 0 {
    didSet {
        if x == 9 { x = 10 }
    }
}

Can I rely on this? Is it documented somewhere? I don't see it in the Swift Programming Language document.

Rob N
  • 15,024
  • 17
  • 92
  • 165
  • Just tested on Xcode 8 Playground (Swift 3) and got the same result. If your change the property from `didSet`, the `didSet` is __not__ called again. – Luca Angeletti Oct 02 '16 at 16:34
  • "Can I rely on this" Yes. – matt Oct 02 '16 at 16:42
  • I actually had a `didSet` on my array which at the end also had a `defer` used for popping the *last* element. Guess what happened? It crashed because I was popping every element which made my array empty, had to do an `if else` inside the `defer` so I won't pop if `myArray.isEmpty != true` – mfaani Jul 18 '17 at 15:04

3 Answers3

18

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.

FelixSFD
  • 6,052
  • 10
  • 43
  • 117
18

From Apple docs (emphasis mine):

Similarly, if you implement a didSet observer, it’s passed a constant parameter containing the old property value. You can name the parameter or use the default parameter name of oldValue. If you assign a value to a property within its own didSet observer, the new value that you assign replaces the one that was just set.

So, assigning a value in didSet is officially OK and won't trigger an infinite recursion.

FreeNickname
  • 7,398
  • 2
  • 30
  • 60
1

It'll work just fine, but it seems pretty like a pretty bad idea from the standpoint of a consumer of your API.

It doesn't recurse, the way I suspected it might, so that's good at least.

I can think of few cases in which it would be acceptable for a setter to change what i'm setting. One such example might be a variable that's set to an angle, which is automatically normalized to be [0, 2π].

Alexander
  • 59,041
  • 12
  • 98
  • 151
  • Did this change on Swift 3? I remember in Swift 2 the recursion call was actually in place. – Luca Angeletti Oct 02 '16 at 16:35
  • In my case I'm rounding a double to nearest of some other values. It doesn't seem worth it to crash the app if they don't set a pre-rounded value. – Rob N Oct 02 '16 at 16:53