Rob N
Rob N

Reputation: 16449

In Swift, does resetting the property inside didSet trigger another didSet?

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.

Upvotes: 21

Views: 13453

Answers (4)

Fattie
Fattie

Reputation: 12206

In Swift5, 2024, there is no recursion.

var dogs: [Dog] = [] {
    didSet {
        print("sorting")
        dogs.sort{
            $0.cat.caseInsensitiveCompare($1.cat) == .orderedAscending
        }
    }
}

It runs only once.

var dogs: [Dog] = [] {
    didSet {
        print("assigning")
        dogs = []
    }
}

It runs only once.

Tested!

Upvotes: -1

FreeNickname
FreeNickname

Reputation: 7843

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.

Upvotes: 21

FelixSFD
FelixSFD

Reputation: 6102

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.

Upvotes: 18

Alexander
Alexander

Reputation: 63399

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π].

Upvotes: 1

Related Questions