Swift: how to change a property's value without calling its didSet function
Asked Answered
P

4

26

How can you set a property's value in Swift, without calling its didSet() function outside of an initialization context? The code below was a failed experiment to achieve this within the classes' noside() function

class Test
{
    var toggle : Bool = 0
    var hoodwink : Int = 0 {
        didSet(hoodwink)
        {
            toggle = !toggle
        }
    }

// failed attempt to set without a side effect

    func noside(newValue : Int)
    {
        hoodwink = newValue
        println("hoodwink: \(hoodwink) state: \(toggle)")
    }

    func withside(newValue : Int)
    {
        self.hoodwink = newValue
        println("hoodwink: \(hoodwink) state: \(toggle)")
    }
}

It is quite trivial to do in Objective-C with auto-synthesized properties:

With side effect (if present in setter):

self.hoodwink = newValue;

Without side effect:

_hoodwink = newValue;
Pilarpilaster answered 19/8, 2014 at 22:48 Comment(0)
E
10

What you do in Objective-C to "avoid side effects" is accessing the backing store of the property - its instance variable, which is prefixed with underscore by default (you can change this using the @synthesize directive).

However, it looks like Swift language designers took specific care to make it impossible to access the backing variables for properties: according to the book,

If you have experience with Objective-C, you may know that it provides two ways to store values and references as part of a class instance. In addition to properties, you can use instance variables as a backing store for the values stored in a property.

Swift unifies these concepts into a single property declaration. A Swift property does not have a corresponding instance variable, and the backing store for a property is not accessed directly. (emphasis is mine)

Of course this applies only to using the "regular language" means, as opposed to using reflection: it might provide a way around this restriction, at the expense of readability.

Encyclopedist answered 19/8, 2014 at 23:6 Comment(1)
True. An addendum: You can always use a separate, private, stored property if you want to manage access to a property's "backing store".Felizio
O
14

A possible hack around this is to provide a setter which bypasses your didSet

 var dontTriggerObservers:Bool = false
    var selectedIndexPath:NSIndexPath? {
        didSet {
            if(dontTriggerObservers == false){
                //blah blah things to do
            }
        }
    }
    var primitiveSetSelectedIndexPath:NSIndexPath? {
        didSet(indexPath) {
            dontTriggerObservers = true
            selectedIndexPath = indexPath
            dontTriggerObservers = false
        }
    }

Ugly but workable

Ophthalmoscopy answered 13/12, 2014 at 19:25 Comment(1)
pretty smart. Would go with var triggerObservers = true myself, so as to avoid double negatives.Drivein
E
10

What you do in Objective-C to "avoid side effects" is accessing the backing store of the property - its instance variable, which is prefixed with underscore by default (you can change this using the @synthesize directive).

However, it looks like Swift language designers took specific care to make it impossible to access the backing variables for properties: according to the book,

If you have experience with Objective-C, you may know that it provides two ways to store values and references as part of a class instance. In addition to properties, you can use instance variables as a backing store for the values stored in a property.

Swift unifies these concepts into a single property declaration. A Swift property does not have a corresponding instance variable, and the backing store for a property is not accessed directly. (emphasis is mine)

Of course this applies only to using the "regular language" means, as opposed to using reflection: it might provide a way around this restriction, at the expense of readability.

Encyclopedist answered 19/8, 2014 at 23:6 Comment(1)
True. An addendum: You can always use a separate, private, stored property if you want to manage access to a property's "backing store".Felizio
C
1

If you exactly know when you want to apply side effects just make it explicitly:

1 Solution:

func noside(newValue : Int) {
    hoodwink = newValue
}

func withside(newValue : Int) {
    self.hoodwink = newValue
    toggle = !toggle
}

2 Solution:

var toggle : Bool = false
var hoodwink : Int = 0 
var hoodwinkToggle: Int {
    get { return hoodwink }
    set(newValue) {
        hoodwink = newValue
        toggle = !toggle
    }
}
  1. func setHoodwinkWithToggle(hoodwink: Int) {...}
  2. ....

I think these solutions will be more clear and readable, then using one variable which at some calls should have side effects and shouldn't at others.

Clinch answered 13/12, 2014 at 20:43 Comment(0)
M
0

The issue I was trying to solve is I wanted to get a doCommonUpdate call when one value changed, or one if both changed at the same time. Doing this created a recursion because if either value changed it would call didSet each time. Example setup, mine was more involved:

class Person {
    var first: String = "" {
        didSet {
            updateValues(first: first, last: last)
        }
    }
    var last: String = "" {
        didSet {
            updateValues(first: first, last: last)
        }
    }
    init() {}
    init(first: String, last: String) {
        self.first = first
        self.last = last
    }
    // also want to set both at the same time.
    func updateValues(first: String, last: String) {
//        self.first = first // causes recursion.
//        self.last = last
        doCommonSetup(first: first, last: last)
    }
    func doCommonSetup(first: String, last: String) {
        print(first, last)
    }
}
let l = Person()
l.first = "Betty"
l.last = "Rubble"
_ = Person(first: "Wilma", last: "Flintstone")

> Betty 
> Betty Rubble

Note Wilma does not print because of the commented out lines.

My solution was to move all those variables into a separate struct. The this approach solves the problem and has the side benefit of creating a another grouping which helps meaning. We still get the doCommonSetup when either value changes independently and when we get both values changed at the same time.

class Person2 {
    struct Name {
        let first: String
        let last: String
    }
    var name: Name
    init() {
        name = Name(first: "", last: "")
    }
    init(first: String, last: String) {
        name = Name(first: first, last: last)
        updateValues(name: name)
    }
    var first: String = "" {
        didSet {
            name = Name(first: first, last: self.last)
            updateValues(name: name)
        }
    }
    var last: String = "" {
        didSet {
            name = Name(first: self.first, last: last)
            updateValues(name: name)
        }
    }
    // also want to set both at the same time.
    func updateValues(name: Name) {
        self.name = name
        doCommonSetup(name: name)
    }
    func doCommonSetup(name: Name) {
        print(name.first, name.last)
    }
}

let p = Person2()
p.first = "Barney"
p.last = "Rubble"
_ = Person2(first: "Fred", last: "Flintstone")

> Barney 
> Barney Rubble
> Fred Flintstone
Melleta answered 14/5, 2018 at 7:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.