7

In many examples of didSet I see on SO, this code will return 0, however, I can't get it to return anything other than the original value. What am I doing wrong?

Swift

struct Circle {
    var radius: Double {
        didSet {
            if radius < 0 {
                radius = 0
            }
        }
    }
}

var circ = Circle(radius: -25)

print(circ.radius)

Output

-25
nipponese
  • 2,813
  • 6
  • 35
  • 51

5 Answers5

22

didSet isn't called during initialization, only afterwards. If you want to validate data during initialization, your initializer should do it.

If you add:

circ.radius = -50
print(circ.radius)

you'll see it working as you'd expect, and the output will be 0.0.

Crowman
  • 25,242
  • 5
  • 48
  • 56
15

You can make sure didSet in the init, if you put it in a defer statement. Also works in deinit.

class Circle {
    var radius: Double {
       didSet {
          if radius < 0 {
             radius = 0
          }
       }
    }

    init(radius: Double) {
        defer { self.radius = radius }
    }
}
Dan Morrow
  • 4,433
  • 2
  • 31
  • 45
7

As written by Paul in the comments, property observers didSet and willSet are not called during value initialization.

If you want to call them for a value also on initialization, you could add a function call to re-set the radius property after it has been initially set in the initializer:

struct Circle {
    var radius: Double {
        didSet {
            if radius < 0 {
                radius = 0
            }
        }
    }

    init(radius: Double) {
        self.radius = radius
        setRadius(radius) // <-- this will invoke didSet
    }

    mutating func setRadius(radius: Double) {
        self.radius = radius
    }
}

var circ = Circle(radius: -25)

print(circ.radius) // 0.0
dfrib
  • 70,367
  • 12
  • 127
  • 192
  • 1
    Just curious, why do we need the first `self.radius = radius` in `init` when the second declaration in `setRadius` is identical, in addition to actually triggering `didSet`? – nipponese Nov 28 '17 at 07:24
  • 3
    Try removing it and you'll be prompted with a quite telling error: "`'self'` used before all stored properties are initialized". Attempting to access the (`mutating`) function `setRadius(radius:)` is equivalent to attempting to use `self`, which is not allowed until `self` has been fully initialized. – dfrib Nov 28 '17 at 08:20
2

Also you can use lazy and init(radius ....

struct Circle {
    lazy var radius: Double = 0.0 {
        didSet {
            print("didSet called")
            if radius < 0 {
                radius = 0
            }
        }
    }
    init(radius:Double) {
        self.radius = radius
    }
}
alexOXz
  • 21
  • 2
  • This worked best for my use case compared to the other suggestions above. Just wondering, why does this not work without lazy ? – Shawn Frank Dec 11 '21 at 03:44
0

Use init for this:

struct Circle {
    var radius: Double

    init(radius: Double) {
        self.radius = radius < 0 ? 0 : radius
    }
}

var circ = Circle(radius: -25)

print(circ.radius)

0.0
malhal
  • 26,330
  • 7
  • 115
  • 133